archgraph-argo 0.27.0 → 0.28.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.
@@ -1,142 +1,227 @@
1
- // dsh-argo-workspace - single source of truth for the DSH workspace bridge.
2
- // install-argo.ps1 copies this file verbatim into ~/.dsh/plugins, so the
3
- // deployed plugin and this shipped bundle artifact are byte-identical.
4
- //
5
- // Direct MCP bridge to the argo server for DeepSeek Harness. Registers every
6
- // argo tool as mcp__argo__* and injects the current session's workspace
7
- // directory (SessionHeader.cwd) as the per-call `workspaceRoot` argument, so
8
- // one dsh instance follows whichever workspace the user switched to - the
9
- // model sees no extra parameters and no internal tool names. The argo server
10
- // honors the injected workspaceRoot unconditionally.
11
- //
12
- // Zero dependencies on purpose: implements the minimal MCP stdio client
13
- // (JSON-RPC 2.0, one JSON object per line) with Node built-ins only, so the
14
- // plugin runs from any DSH layout without resolving @modelcontextprotocol/sdk.
15
- import { spawn } from 'node:child_process'
16
- import readline from 'node:readline'
17
- import { fileURLToPath } from 'node:url'
18
-
19
- export const name = 'dsh-argo-workspace'
20
- export const inject = ['tools']
21
-
22
- // 包内默认:<pkg>/argo/scripts/argo-mcp-server.js —— 随包走,跨机器可移植。
23
- const DEFAULT_SERVER_PATH = fileURLToPath(
24
- new URL('../argo/scripts/argo-mcp-server.js', import.meta.url),
25
- )
26
-
27
- /** Minimal MCP stdio client over the argo server process. */
28
- function createArgoClient(serverPath, env, cwd) {
29
- const child = spawn('node', [serverPath], {
30
- stdio: ['pipe', 'pipe', 'pipe'],
31
- env,
32
- ...(cwd ? { cwd } : {}),
33
- })
34
- const pending = new Map()
35
- let nextId = 1
36
- readline.createInterface({ input: child.stdout }).on('line', (line) => {
37
- let message
38
- try { message = JSON.parse(line) } catch { return }
39
- if (message && typeof message.id === 'number' && pending.has(message.id)) {
40
- const { resolve, reject } = pending.get(message.id)
41
- pending.delete(message.id)
42
- if (message.error) reject(new Error(JSON.stringify(message.error)))
43
- else resolve(message.result)
44
- }
45
- })
46
- child.stderr.on('data', () => {}) // drain; the argo server logs to stderr
47
- const request = (method, params) => new Promise((resolve, reject) => {
48
- const id = nextId++
49
- pending.set(id, { resolve, reject })
50
- child.stdin.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n')
51
- })
52
- return {
53
- request,
54
- notify: (method, params) => {
55
- child.stdin.write(JSON.stringify({ jsonrpc: '2.0', method, params }) + '\n')
56
- },
57
- close: () => child.kill(),
58
- }
59
- }
60
-
61
- /** Connect, list tools, register them, and keep the client until disposal. */
62
- export async function apply(ctx, config) {
63
- const serverPath = config.serverPath
64
- ?? process.env.ARGO_SERVER_PATH
65
- ?? DEFAULT_SERVER_PATH
66
- const workspaces = Array.isArray(config.workspaces) ? config.workspaces : []
67
- const env = { ...process.env }
68
- if (workspaces.length > 0) env.ARGO_WORKSPACE_ROOTS = workspaces.join(';')
69
- const client = createArgoClient(serverPath, env, config.cwd)
70
- ctx.effect(() => () => client.close(), 'dsh-argo-workspace.dispose()')
71
-
72
- let tools = []
73
- try {
74
- await client.request('initialize', {
75
- protocolVersion: '2024-11-05',
76
- capabilities: {},
77
- clientInfo: { name: 'dsh-argo-workspace', version: '0.1.0' },
78
- })
79
- client.notify('notifications/initialized', {})
80
- const listed = await client.request('tools/list', {})
81
- tools = (listed && Array.isArray(listed.tools)) ? listed.tools : []
82
- } catch (error) {
83
- console.warn(`[dsh-argo-workspace] failed to connect to the argo MCP server (${serverPath}): ${error && error.message ? error.message : error}`)
84
- return
85
- }
86
-
87
- const disposers = []
88
- for (const tool of tools) {
89
- const publicName = 'mcp__argo__' + tool.name
90
- disposers.push(ctx.tools.register({
91
- name: publicName,
92
- description: tool.description ?? '',
93
- parameters: tool.inputSchema,
94
- output: {
95
- schema: {
96
- type: 'object',
97
- properties: {
98
- content: { type: 'array', items: {} },
99
- structuredContent: {},
100
- },
101
- required: ['content'],
102
- additionalProperties: false,
103
- },
104
- render: (_args, value) => value.content,
105
- },
106
- execute: async (args, exec) => {
107
- // SessionHeader.cwd (the durable session workspace), NOT
108
- // requestHeader() — that returns the request EpochHeader
109
- // (config/system/tools) which has no cwd field.
110
- const sessionCwd = exec && exec.agent && exec.agent.session
111
- ? exec.agent.session.header?.cwd
112
- : undefined
113
- const injected = { ...(args && typeof args === 'object' ? args : {}) }
114
- if (typeof sessionCwd === 'string' && sessionCwd !== '') {
115
- injected.workspaceRoot = sessionCwd
116
- }
117
- if (exec && exec.signal && exec.signal.aborted) {
118
- throw new Error('aborted')
119
- }
120
- const result = await client.request('tools/call', {
121
- name: tool.name,
122
- arguments: injected,
123
- })
124
- const text = Array.isArray(result.content)
125
- ? result.content
126
- .map((block) => block && block.type === 'text' && typeof block.text === 'string'
127
- ? block.text
128
- : JSON.stringify(block))
129
- .join('\n')
130
- : (result.toolResult !== undefined ? JSON.stringify(result.toolResult) : '(no output)')
131
- if (result.isError === true) throw new Error(text)
132
- return {
133
- content: [{ type: 'text', text }],
134
- ...(result.structuredContent !== undefined ? { structuredContent: result.structuredContent } : {}),
135
- }
136
- },
137
- }))
138
- }
139
- ctx.effect(() => () => {
140
- for (const dispose of disposers) dispose()
141
- }, 'dsh-argo-workspace.tools')
142
- }
1
+ // dsh-argo-workspace - single source of truth for the DSH workspace bridge.
2
+ // install-argo.ps1 copies this file verbatim into ~/.dsh/plugins, so the
3
+ // deployed plugin and this shipped bundle artifact are byte-identical.
4
+ //
5
+ // Direct MCP stdio bridge(es) for DeepSeek Harness. Registers every tool of each
6
+ // configured MCP server as mcp__<id>__* and, for the argo server, injects the
7
+ // current session's workspace directory (SessionHeader.cwd) as the per-call
8
+ // `workspaceRoot` argument, so one dsh instance follows whichever workspace the
9
+ // user switched to - the model sees no extra parameters and no internal tool
10
+ // names. The argo server honors the injected workspaceRoot unconditionally.
11
+ //
12
+ // Multiple servers are supported so a host that can only spawn local stdio
13
+ // children (DSH) can still mount the remote federation Graph MCP through the
14
+ // stdio bridge (GRAPH_MCP_URL). Configure via `config.servers` (a list); the
15
+ // legacy single-server shape (`config.serverPath`) is still honored.
16
+ //
17
+ // Zero dependencies on purpose: implements the minimal MCP stdio client
18
+ // (JSON-RPC 2.0, one JSON object per line) with Node built-ins only, so the
19
+ // plugin runs from any DSH layout without resolving @modelcontextprotocol/sdk.
20
+ import { spawn } from 'node:child_process'
21
+ import readline from 'node:readline'
22
+ import { fileURLToPath } from 'node:url'
23
+
24
+ export const name = 'dsh-argo-workspace'
25
+ export const inject = ['tools']
26
+
27
+ // 包内默认:<pkg>/argo/scripts/argo-mcp-server.js —— 随包走,跨机器可移植。
28
+ const DEFAULT_SERVER_PATH = fileURLToPath(
29
+ new URL('../argo/scripts/argo-mcp-server.js', import.meta.url),
30
+ )
31
+
32
+ // A `serverPath`/`args` entry that starts with ./ or ../ resolves relative to this
33
+ // plugin module (portable bundle references); an absolute path is used as-is.
34
+ function resolveMaybeRelative(value) {
35
+ if (typeof value === 'string' && /^[.][\\/]/.test(value)) {
36
+ try {
37
+ return fileURLToPath(new URL(value, import.meta.url))
38
+ } catch {
39
+ return value
40
+ }
41
+ }
42
+ return value
43
+ }
44
+
45
+ /** Normalize config into a list of server specs (backward compatible). */
46
+ function normalizeServerSpecs(config) {
47
+ const topWorkspaces = Array.isArray(config.workspaces) ? config.workspaces : []
48
+ const withCommon = (spec) => {
49
+ const env = { ...process.env, ...(spec.env || {}) }
50
+ if (spec.injectWorkspaceRoot && topWorkspaces.length > 0) {
51
+ env.ARGO_WORKSPACE_ROOTS = topWorkspaces.join(';')
52
+ }
53
+ // Assert the broker-controlled workspace channel: the argo server will only
54
+ // trust the per-call sessionWorkspaceRoot (which this bridge sets from the
55
+ // durable SessionHeader.cwd, overwriting any model value) when this flag is
56
+ // present. The model cannot set a server process env var, so the trust is
57
+ // unforgeable.
58
+ if (spec.injectWorkspaceRoot) {
59
+ env.ARGO_WORKSPACE_ROOT_TRUSTED = '1'
60
+ }
61
+ return { ...spec, env, cwd: spec.cwd ?? (spec.injectWorkspaceRoot ? config.cwd : undefined) }
62
+ }
63
+
64
+ if (Array.isArray(config.servers) && config.servers.length > 0) {
65
+ return config.servers.map((server) => {
66
+ const id = typeof server.id === 'string' && server.id !== '' ? server.id : 'server'
67
+ const injectWorkspaceRoot = server.injectWorkspaceRoot !== undefined
68
+ ? server.injectWorkspaceRoot === true
69
+ : id === 'argo'
70
+ const serverPath = resolveMaybeRelative(server.serverPath)
71
+ ?? (id === 'argo' ? DEFAULT_SERVER_PATH : undefined)
72
+ const args = Array.isArray(server.args)
73
+ ? server.args.map(resolveMaybeRelative)
74
+ : (serverPath ? [serverPath] : [])
75
+ return withCommon({
76
+ id,
77
+ // mcp__<id>__ prefix mirrors the host's server-name convention.
78
+ prefix: typeof server.prefix === 'string' && server.prefix !== '' ? server.prefix : 'mcp__' + id + '__',
79
+ command: typeof server.command === 'string' && server.command !== '' ? server.command : 'node',
80
+ args,
81
+ env: server.env && typeof server.env === 'object' ? server.env : {},
82
+ cwd: server.cwd,
83
+ injectWorkspaceRoot,
84
+ })
85
+ })
86
+ }
87
+
88
+ // Legacy single-server shape: the argo workspace bridge.
89
+ const serverPath = config.serverPath ?? process.env.ARGO_SERVER_PATH ?? DEFAULT_SERVER_PATH
90
+ return [withCommon({
91
+ id: 'argo',
92
+ prefix: 'mcp__argo__',
93
+ command: 'node',
94
+ args: [serverPath],
95
+ env: {},
96
+ cwd: config.cwd,
97
+ injectWorkspaceRoot: true,
98
+ })]
99
+ }
100
+
101
+ /** Minimal MCP stdio client over one spawned server process. */
102
+ function createStdioClient(command, args, env, cwd) {
103
+ const child = spawn(command, args, {
104
+ stdio: ['pipe', 'pipe', 'pipe'],
105
+ env,
106
+ ...(cwd ? { cwd } : {}),
107
+ })
108
+ const pending = new Map()
109
+ let nextId = 1
110
+ readline.createInterface({ input: child.stdout }).on('line', (line) => {
111
+ let message
112
+ try { message = JSON.parse(line) } catch { return }
113
+ if (message && typeof message.id === 'number' && pending.has(message.id)) {
114
+ const { resolve, reject } = pending.get(message.id)
115
+ pending.delete(message.id)
116
+ if (message.error) reject(new Error(JSON.stringify(message.error)))
117
+ else resolve(message.result)
118
+ }
119
+ })
120
+ child.stderr.on('data', () => {}) // drain; the server logs to stderr
121
+ const request = (method, params) => new Promise((resolve, reject) => {
122
+ const id = nextId++
123
+ pending.set(id, { resolve, reject })
124
+ child.stdin.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n')
125
+ })
126
+ return {
127
+ request,
128
+ notify: (method, params) => {
129
+ child.stdin.write(JSON.stringify({ jsonrpc: '2.0', method, params }) + '\n')
130
+ },
131
+ close: () => child.kill(),
132
+ }
133
+ }
134
+
135
+ /** Connect one server, list its tools, and register them under its prefix. */
136
+ async function mountServer(ctx, spec, registered) {
137
+ const client = createStdioClient(spec.command, spec.args, spec.env, spec.cwd)
138
+ ctx.effect(() => () => client.close(), `dsh-argo-workspace.dispose(${spec.id})`)
139
+
140
+ let tools = []
141
+ try {
142
+ await client.request('initialize', {
143
+ protocolVersion: '2024-11-05',
144
+ capabilities: {},
145
+ clientInfo: { name: 'dsh-argo-workspace', version: '0.1.0' },
146
+ })
147
+ client.notify('notifications/initialized', {})
148
+ const listed = await client.request('tools/list', {})
149
+ tools = (listed && Array.isArray(listed.tools)) ? listed.tools : []
150
+ } catch (error) {
151
+ console.warn(`[dsh-argo-workspace] failed to connect to '${spec.id}' (${spec.args.join(' ')}): ${error && error.message ? error.message : error}`)
152
+ return
153
+ }
154
+
155
+ for (const tool of tools) {
156
+ const publicName = spec.prefix + tool.name
157
+ registered.push(ctx.tools.register({
158
+ name: publicName,
159
+ description: tool.description ?? '',
160
+ parameters: tool.inputSchema,
161
+ output: {
162
+ schema: {
163
+ type: 'object',
164
+ properties: {
165
+ content: { type: 'array', items: {} },
166
+ structuredContent: {},
167
+ },
168
+ required: ['content'],
169
+ additionalProperties: false,
170
+ },
171
+ render: (_args, value) => value.content,
172
+ },
173
+ execute: async (args, exec) => {
174
+ // SessionHeader.cwd (the durable session workspace), NOT
175
+ // requestHeader() — that returns the request EpochHeader
176
+ // (config/system/tools) which has no cwd field.
177
+ const injected = { ...(args && typeof args === 'object' ? args : {}) }
178
+ if (spec.injectWorkspaceRoot) {
179
+ const sessionCwd = exec && exec.agent && exec.agent.session
180
+ ? exec.agent.session.header?.cwd
181
+ : undefined
182
+ if (typeof sessionCwd === 'string' && sessionCwd !== '') {
183
+ injected.workspaceRoot = sessionCwd
184
+ // Broker-only trusted channel: the server allows WRITES to this
185
+ // workspace (see workspace-write-guard.js). Set AFTER the spread so
186
+ // a model-supplied value can never win.
187
+ injected.sessionWorkspaceRoot = sessionCwd
188
+ } else {
189
+ // No session workspace: never forward a model-supplied trusted field.
190
+ delete injected.sessionWorkspaceRoot
191
+ }
192
+ }
193
+ if (exec && exec.signal && exec.signal.aborted) {
194
+ throw new Error('aborted')
195
+ }
196
+ const result = await client.request('tools/call', {
197
+ name: tool.name,
198
+ arguments: injected,
199
+ })
200
+ const text = Array.isArray(result.content)
201
+ ? result.content
202
+ .map((block) => block && block.type === 'text' && typeof block.text === 'string'
203
+ ? block.text
204
+ : JSON.stringify(block))
205
+ .join('\n')
206
+ : (result.toolResult !== undefined ? JSON.stringify(result.toolResult) : '(no output)')
207
+ if (result.isError === true) throw new Error(text)
208
+ return {
209
+ content: [{ type: 'text', text }],
210
+ ...(result.structuredContent !== undefined ? { structuredContent: result.structuredContent } : {}),
211
+ }
212
+ },
213
+ }))
214
+ }
215
+ }
216
+
217
+ /** Connect every configured server, register its tools, keep clients until disposal. */
218
+ export async function apply(ctx, config) {
219
+ const specs = normalizeServerSpecs(config || {})
220
+ const registered = []
221
+ for (const spec of specs) {
222
+ await mountServer(ctx, spec, registered)
223
+ }
224
+ ctx.effect(() => () => {
225
+ for (const dispose of registered) dispose()
226
+ }, 'dsh-argo-workspace.tools')
227
+ }
package/install-argo.ps1 CHANGED
@@ -28,6 +28,9 @@ param(
28
28
  [string]$DoubaoHome = "$env:USERPROFILE\Doubao",
29
29
  [string]$DoubaoWorkspace = "$env:LOCALAPPDATA\Doubao\User Data\Default\.doubao\agent_mode\workspace",
30
30
  [switch]$SkipDoubao,
31
+ [string]$CodexHome = "$env:USERPROFILE\.codex",
32
+ [string]$CodexRepoRoot = '',
33
+ [switch]$SkipCodex,
31
34
  [string]$McpPath,
32
35
  [string]$GraphMcpUrl = 'https://argo.derekworkspacev5.com/mcp'
33
36
  )
@@ -596,6 +599,71 @@ function Write-OpenClawMcpConfig {
596
599
  [System.IO.File]::WriteAllText($configPath, $json, (New-Object System.Text.UTF8Encoding $false))
597
600
  }
598
601
 
602
+ function ConvertTo-TomlString {
603
+ # Encode a value as a TOML basic string (escape backslash + double quote).
604
+ param([string]$Value)
605
+ return '"' + ($Value -replace '\\', '\\\\' -replace '"', '\"') + '"'
606
+ }
607
+
608
+ function Write-CodexMcpConfig {
609
+ # Register the argo and graph-mcp MCP servers in the Codex config file
610
+ # (~/.codex/config.toml). Codex stores MCP servers as standard TOML tables
611
+ # [mcp_servers.<name>] (NOT the JSON/YAML shapes the other hosts use), so
612
+ # this is a text-level table upsert:
613
+ # - the previous managed tables ([mcp_servers.argo], [mcp_servers.argo.env],
614
+ # [mcp_servers.graph-mcp]) are removed first, so a re-deploy replaces
615
+ # rather than duplicates them (duplicate TOML tables are a parse error);
616
+ # - every unrelated config.toml key (the desktop app's own settings, other
617
+ # MCP servers, plugins, marketplaces) is preserved verbatim;
618
+ # - the file is written without a BOM.
619
+ #
620
+ # Workspace: Codex spawns every stdio MCP server with the SESSION workspace
621
+ # as its process cwd and without ARGO_* env (verified), and argo-paths.js
622
+ # falls back to process.cwd() when ARGO_REPO_ROOT is unset. So, like
623
+ # OpenCode / DeepSeek Harness, the single global argo server FOLLOWS whichever
624
+ # workspace the user opened in Codex and ARGO_REPO_ROOT is NOT pinned by
625
+ # default. Pass -RepoRoot to pin a fixed workspace instead (opt-in).
626
+ param(
627
+ [string]$ConfigPath,
628
+ [string]$ArgoServer,
629
+ [string]$GraphMcpUrl,
630
+ [string]$RepoRoot
631
+ )
632
+ $targetHeaders = @('[mcp_servers.argo]', '[mcp_servers.argo.env]', '[mcp_servers.graph-mcp]')
633
+ $existing = if (Test-Path $ConfigPath) { [System.IO.File]::ReadAllText($ConfigPath) } else { '' }
634
+ $lines = if ($existing -eq '') { @() } else { $existing -split "\r?\n" }
635
+
636
+ $kept = New-Object System.Collections.Generic.List[string]
637
+ $i = 0
638
+ while ($i -lt $lines.Count) {
639
+ $trim = $lines[$i].Trim()
640
+ if ($targetHeaders -contains $trim) {
641
+ # Skip the header and its body up to the next top-level table header.
642
+ $i++
643
+ while ($i -lt $lines.Count -and -not $lines[$i].TrimStart().StartsWith('[')) { $i++ }
644
+ continue
645
+ }
646
+ $kept.Add($lines[$i])
647
+ $i++
648
+ }
649
+ while ($kept.Count -gt 0 -and $kept[$kept.Count - 1].Trim() -eq '') { $kept.RemoveAt($kept.Count - 1) }
650
+
651
+ $blocks = New-Object System.Collections.Generic.List[string]
652
+ $blocks.Add((@('[mcp_servers.argo]', 'command = "node"', "args = [$(ConvertTo-TomlString $ArgoServer)]") -join "`n"))
653
+ if (-not [string]::IsNullOrWhiteSpace($RepoRoot)) {
654
+ $blocks.Add(("[mcp_servers.argo.env]`nARGO_REPO_ROOT = $(ConvertTo-TomlString $RepoRoot)"))
655
+ }
656
+ $blocks.Add(("[mcp_servers.graph-mcp]`nurl = $(ConvertTo-TomlString $GraphMcpUrl)"))
657
+
658
+ $result = ($kept -join "`n")
659
+ if ($result.Trim() -ne '') { $result = $result.TrimEnd() + "`n`n" }
660
+ $result += ($blocks -join "`n`n") + "`n"
661
+
662
+ New-Item -ItemType Directory -Force -Path (Split-Path $ConfigPath) | Out-Null
663
+ [System.IO.File]::WriteAllText($ConfigPath, $result, (New-Object System.Text.UTF8Encoding $false))
664
+ Write-Host " Codex MCP: argo (stdio) + graph-mcp (streamable http) -> $ConfigPath"
665
+ }
666
+
599
667
  function Write-DshManagedBlock {
600
668
  # Text-level upsert of a marker-delimited block into a file (used for the
601
669
  # managed rows in ~/.dsh/cordis.patch.yml): replaces the previous managed
@@ -776,10 +844,9 @@ Write-Host "[4/22] argo\skills\argo-init -> $skillDest"
776
844
  Copy-Tree -Source $skillSrc -Destination $skillDest
777
845
  $reconcileSkillSrc = Join-Path (Join-Path $argoDir 'skills') 'ea-human-reconcile'
778
846
  $diagSkillSrc = Join-Path (Join-Path $argoDir 'skills') 'agent-search-diagnosis'
779
- Write-Host ' argo\skills\ea-human-reconcile -> $SkillsRoot\ea-human-reconcile (EA human draft reconcile skill)'
847
+ Write-Host " argo\skills\ea-human-reconcile -> $SkillsRoot\ea-human-reconcile (EA human draft reconcile skill)"
780
848
  Copy-Tree -Source $reconcileSkillSrc -Destination (Join-Path $SkillsRoot 'ea-human-reconcile')
781
- Write-Host ' argo\skills\agent-search-diagnosis -> $SkillsRoot\agent-search-diagnosis (agent search diagnosis skill)'
782
- Copy-Tree -Source $diagSkillSrc -Destination (Join-Path $SkillsRoot 'agent-search-diagnosis')
849
+ # agent-search-diagnosis is OpenCode-only (it assumes `opencode` session export); not deployed to Copilot.
783
850
 
784
851
  $ruleSrc = Join-Path (Join-Path $argoDir 'rules') 'archgraph.instructions.md'
785
852
  $ruleDest = Join-Path $PromptsRoot 'archgraph.instructions.md'
@@ -795,10 +862,9 @@ Copy-Item -Force -Path $depsSrc -Destination $depsDest
795
862
  $cursorSkillDest = Join-Path $CursorSkillsRoot 'argo-init'
796
863
  Write-Host "[7/22] argo\skills\argo-init -> $cursorSkillDest (Cursor)"
797
864
  Copy-Tree -Source $skillSrc -Destination $cursorSkillDest
798
- Write-Host ' argo\skills\ea-human-reconcile -> $CursorSkillsRoot\ea-human-reconcile (Cursor)'
865
+ Write-Host " argo\skills\ea-human-reconcile -> $CursorSkillsRoot\ea-human-reconcile (Cursor)"
799
866
  Copy-Tree -Source $reconcileSkillSrc -Destination (Join-Path $CursorSkillsRoot 'ea-human-reconcile')
800
- Write-Host ' argo\skills\agent-search-diagnosis -> $CursorSkillsRoot\agent-search-diagnosis (Cursor)'
801
- Copy-Tree -Source $diagSkillSrc -Destination (Join-Path $CursorSkillsRoot 'agent-search-diagnosis')
867
+ # agent-search-diagnosis is OpenCode-only (it assumes `opencode` session export); not deployed to Cursor.
802
868
 
803
869
  $mcpBridgeSrc = Join-Path $argoDir 'mcp-bridges'
804
870
  $mcpBridgeDest = Join-Path $CursorMcpBridgesRoot ''
@@ -808,9 +874,9 @@ Copy-Tree -Source $mcpBridgeSrc -Destination $mcpBridgeDest
808
874
  $openCodeSkillDest = Join-Path $OpenCodeSkillsRoot 'argo-init'
809
875
  Write-Host "[8/22] argo\skills\argo-init -> $openCodeSkillDest (OpenCode)"
810
876
  Copy-Tree -Source $skillSrc -Destination $openCodeSkillDest
811
- Write-Host ' argo\skills\ea-human-reconcile -> $OpenCodeSkillsRoot\ea-human-reconcile (OpenCode)'
877
+ Write-Host " argo\skills\ea-human-reconcile -> $OpenCodeSkillsRoot\ea-human-reconcile (OpenCode)"
812
878
  Copy-Tree -Source $reconcileSkillSrc -Destination (Join-Path $OpenCodeSkillsRoot 'ea-human-reconcile')
813
- Write-Host ' argo\skills\agent-search-diagnosis -> $OpenCodeSkillsRoot\agent-search-diagnosis (OpenCode)'
879
+ Write-Host " argo\skills\agent-search-diagnosis -> $OpenCodeSkillsRoot\agent-search-diagnosis (OpenCode-only: assumes opencode session export)"
814
880
  Copy-Tree -Source $diagSkillSrc -Destination (Join-Path $OpenCodeSkillsRoot 'agent-search-diagnosis')
815
881
 
816
882
  Write-Host "[9/22] argo\rules\archgraph.instructions.md -> $OpenCodeAgentsPath (OpenCode global AGENTS.md)"
@@ -849,15 +915,14 @@ if ($SkipDsh) {
849
915
 
850
916
  Write-Host "[16/22] argo\skills\argo-init -> $dshSkillDest (DeepSeek Harness skill)"
851
917
  Copy-Tree -Source (Join-Path $argoDir 'skills\argo-init') -Destination $dshSkillDest
852
- Write-Host ' argo\skills\ea-human-reconcile -> $DshHome\skills\ea-human-reconcile (DeepSeek Harness skill)'
918
+ Write-Host " argo\skills\ea-human-reconcile -> $DshHome\skills\ea-human-reconcile (DeepSeek Harness skill)"
853
919
  Copy-Tree -Source (Join-Path $argoDir 'skills\ea-human-reconcile') -Destination (Join-Path (Join-Path $DshHome 'skills') 'ea-human-reconcile')
854
- Write-Host ' argo\skills\agent-search-diagnosis -> $DshHome\skills\agent-search-diagnosis (DeepSeek Harness skill)'
855
- Copy-Tree -Source $diagSkillSrc -Destination (Join-Path (Join-Path $DshHome 'skills') 'agent-search-diagnosis')
920
+ # agent-search-diagnosis is OpenCode-only (it assumes `opencode` session export); not deployed to DeepSeek Harness.
856
921
 
857
922
  Write-Host "[17/22] dsh-argo-wakeup\index.js (single source; gate from argo\rules\archgraph.instructions.md <WakeupGuideline>) -> $DshHome\plugins\dsh-argo-wakeup\index.js (DeepSeek Harness wakeup plugin)"
858
923
  $wakeupDshPath = New-DshWakeupPlugin -DshHome $DshHome
859
924
 
860
- Write-Host "[18/22] argo-workspace + argo-wakeup rows -> $patchPath (DeepSeek Harness MCP bridge + wakeup plugin)"
925
+ Write-Host "[18/22] argo-workspace (argo + graph-mcp) + argo-wakeup rows -> $patchPath (DeepSeek Harness MCP bridges + wakeup plugin)"
861
926
  # The generated dsh-argo-workspace bridge connects directly to the argo
862
927
  # server (no dsh-mcp-client row), registers every tool as mcp__argo__* and
863
928
  # injects the current session's workspace (SessionHeader.cwd) as the
@@ -865,9 +930,20 @@ if ($SkipDsh) {
865
930
  # user switched to. The server honors the injected workspaceRoot
866
931
  # unconditionally.
867
932
  $bridgeDshPath = New-DshWorkspaceBridge -DshHome $DshHome
933
+ # Deploy the federation Graph MCP stdio bridge next to the DSH plugins, so the
934
+ # remote graph-mcp (registry_*/mirror_*/graph_*) mounts as an ordinary local
935
+ # stdio server - the same bridge Cursor/Doubao already use.
936
+ $dshBridgeDir = Join-Path $DshHome 'mcp-bridges'
937
+ Copy-Tree -Source (Join-Path $argoDir 'mcp-bridges') -Destination $dshBridgeDir
938
+ $dshGraphBridgePath = (Join-Path $dshBridgeDir 'graph-mcp-stdio.js').Replace('\', '/')
868
939
  if ($bridgeDshPath) {
869
940
  $bridgeUrl = 'file:///' + (($bridgeDshPath -replace '\\', '/').TrimStart('/'))
870
- $bridgeConfig = " config:`n serverPath: '$argoServer'`n"
941
+ # One bridge plugin mounts every server; the argo server follows the session
942
+ # workspace (workspaceRoot), graph-mcp does not inject workspaceRoot.
943
+ $serversYaml = " servers:`n"
944
+ $serversYaml += " - id: argo`n serverPath: '$argoServer'`n injectWorkspaceRoot: true`n"
945
+ $serversYaml += " - id: graph-mcp`n command: node`n args:`n - '$dshGraphBridgePath'`n env:`n GRAPH_MCP_URL: '$GraphMcpUrl'`n injectWorkspaceRoot: false`n"
946
+ $bridgeConfig = " config:`n" + $serversYaml
871
947
  if ($DshWorkspaces) {
872
948
  $wsList = @($DshWorkspaces.Replace('\', '/').Split(';') | ForEach-Object { " - $_" }) -join "`n"
873
949
  $bridgeConfig += " workspaces:`n" + $wsList + "`n"
@@ -891,10 +967,8 @@ if ($SkipDsh) {
891
967
  Write-Host "[19/22] argo\agents -> $DshHome\.agent-presets\<id> (DeepSeek Harness agent presets)"
892
968
  New-DshAgentPresets -DshHome $DshHome -AgentsSrc (Join-Path $argoDir 'agents')
893
969
 
894
- Write-Host " note: graph-mcp remote ($GraphMcpUrl) is NOT registered for DeepSeek Harness -"
895
- Write-Host " ~/.dsh/cordis.patch.yml only carries plugin rows (the dsh-argo-workspace bridge spawns"
896
- Write-Host " the local argo server); an HTTP remote MCP server has no equivalent cordis row here."
897
- Write-Host " Restart `dsh web` to activate the MCP bridge and the wakeup plugin;"
970
+ Write-Host " graph-mcp registered for DeepSeek Harness as a stdio bridge -> $dshGraphBridgePath (GRAPH_MCP_URL=$GraphMcpUrl)"
971
+ Write-Host " Restart `dsh web` to activate the MCP bridges and the wakeup plugin;"
898
972
  Write-Host ' new sessions pick up the global rule and the argo-init skill automatically.'
899
973
  Write-Host ' The argo tools (mcp__argo__*) auto-follow the workspace the user switched to'
900
974
  Write-Host ' in one dsh instance - no restart needed between workspaces (the server honors'
@@ -914,10 +988,9 @@ if ($SkipOpenClaw) {
914
988
 
915
989
  Write-Host "[21/22] argo\skills\argo-init -> $openClawSkillDest (OpenClaw managed skill, all agents)"
916
990
  Copy-Tree -Source (Join-Path $argoDir 'skills\argo-init') -Destination $openClawSkillDest
917
- Write-Host ' argo\skills\ea-human-reconcile -> $OpenClawHome\skills\ea-human-reconcile (OpenClaw managed skill, all agents)'
991
+ Write-Host " argo\skills\ea-human-reconcile -> $OpenClawHome\skills\ea-human-reconcile (OpenClaw managed skill, all agents)"
918
992
  Copy-Tree -Source (Join-Path $argoDir 'skills\ea-human-reconcile') -Destination (Join-Path (Join-Path $OpenClawHome 'skills') 'ea-human-reconcile')
919
- Write-Host ' argo\skills\agent-search-diagnosis -> $OpenClawHome\skills\agent-search-diagnosis (OpenClaw managed skill, all agents)'
920
- Copy-Tree -Source $diagSkillSrc -Destination (Join-Path (Join-Path $OpenClawHome 'skills') 'agent-search-diagnosis')
993
+ # agent-search-diagnosis is OpenCode-only (it assumes `opencode` session export); not deployed to OpenClaw.
921
994
 
922
995
  Write-Host ' OpenClaw injects AGENTS.md into Project Context on every session, so the wakeup'
923
996
  Write-Host ' gate (UNCONDITIONAL STARTUP GATE) is active on the next OpenClaw session; restart'
@@ -937,7 +1010,8 @@ if ($SkipDoubao) {
937
1010
  # fully ported surface). MCP servers are registered through Doubao's in-app
938
1011
  # custom connector UI (STDIO), not a config file, so the installer emits a
939
1012
  # copy-paste connector recipe instead of writing a config.
940
- $doubaoSkills = @('argo-init', 'ea-human-reconcile', 'agent-search-diagnosis')
1013
+ # agent-search-diagnosis is OpenCode-only (it assumes `opencode` session export); not deployed to Doubao.
1014
+ $doubaoSkills = @('argo-init', 'ea-human-reconcile')
941
1015
  $doubaoSkillsSrc = Join-Path $argoDir 'skills'
942
1016
  Write-Host '==> Deploying Doubao (desktop agent mode) integration'
943
1017
  Copy-DoubaoSkills -Root (Join-Path $DoubaoWorkspace '.user_skills') -SourceSkillsRoot $doubaoSkillsSrc -SkillNames $doubaoSkills -Label 'workspace .user_skills'
@@ -1142,6 +1216,33 @@ if ($SkipMcp) {
1142
1216
  }
1143
1217
  }
1144
1218
 
1219
+ if ($SkipCodex) {
1220
+ Write-Host 'Skipped Codex integration (-SkipCodex).'
1221
+ } else {
1222
+ $ruleSrcContent = Get-Content $ruleSrc -Raw -Encoding UTF8
1223
+ $codexAgentsPath = Join-Path $CodexHome 'AGENTS.md'
1224
+ $codexSkillsRoot = Join-Path $CodexHome 'skills'
1225
+
1226
+ Write-Host '==> Deploying Codex integration'
1227
+ Write-Host "[Codex 1/3] argo\rules\archgraph.instructions.md -> $codexAgentsPath (Codex global AGENTS.md, frontmatter stripped)"
1228
+ Write-ArchGraphRuleBlock -DestPath $codexAgentsPath -RuleText $ruleSrcContent -Label 'Codex rule'
1229
+
1230
+ Write-Host "[Codex 2/3] argo\skills -> $codexSkillsRoot (Codex user skills: argo-init, ea-human-reconcile)"
1231
+ Copy-DoubaoSkills -Root $codexSkillsRoot -SourceSkillsRoot (Join-Path $argoDir 'skills') -SkillNames @('argo-init', 'ea-human-reconcile') -Label 'Codex user skills'
1232
+ # agent-search-diagnosis is OpenCode-only (it assumes `opencode` session export); not deployed to Codex.
1233
+
1234
+ if ($SkipMcp) {
1235
+ Write-Host ' Skipped Codex MCP registration (-SkipMcp).'
1236
+ } else {
1237
+ $codexConfigPath = Join-Path $CodexHome 'config.toml'
1238
+ Write-Host "[Codex 3/3] argo MCP (stdio) + graph-mcp -> $codexConfigPath (Codex config.toml)"
1239
+ Write-CodexMcpConfig -ConfigPath $codexConfigPath -ArgoServer $argoServer -GraphMcpUrl $GraphMcpUrl -RepoRoot $CodexRepoRoot
1240
+ }
1241
+ Write-Host ' Codex injects the global AGENTS.md into every session, so the wakeup gate is'
1242
+ Write-Host ' active on the next Codex session; skills load on trigger. Restart Codex so it'
1243
+ Write-Host ' picks up config.toml and the new skills.'
1244
+ }
1245
+
1145
1246
  $wakeupPluginPath = Join-Path $PluginsRoot 'argo-wakeup.js'
1146
1247
  if (Test-Path $wakeupPluginPath) {
1147
1248
  Write-Host '==> Registering argo-wakeup plugin in OpenCode'