archgraph-argo 0.26.2 → 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.
package/install-argo.ps1 CHANGED
@@ -25,6 +25,12 @@ param(
25
25
  [string]$OpenClawWorkspace = "$env:USERPROFILE\.openclaw\workspace",
26
26
  [string]$OpenClawRepoRoot = '',
27
27
  [switch]$SkipOpenClaw,
28
+ [string]$DoubaoHome = "$env:USERPROFILE\Doubao",
29
+ [string]$DoubaoWorkspace = "$env:LOCALAPPDATA\Doubao\User Data\Default\.doubao\agent_mode\workspace",
30
+ [switch]$SkipDoubao,
31
+ [string]$CodexHome = "$env:USERPROFILE\.codex",
32
+ [string]$CodexRepoRoot = '',
33
+ [switch]$SkipCodex,
28
34
  [string]$McpPath,
29
35
  [string]$GraphMcpUrl = 'https://argo.derekworkspacev5.com/mcp'
30
36
  )
@@ -298,16 +304,6 @@ function Get-MarkdownBody {
298
304
  return $m.Groups[2].Value
299
305
  }
300
306
 
301
- function Get-WakeupGuideline {
302
- # Extract the <WakeupGuideline>...</WakeupGuideline> block from the rule
303
- # text, so the DSH wakeup plugin is generated from the same source the
304
- # Copilot / Cursor / OpenCode rules use.
305
- param([string]$RuleText)
306
- $m = [regex]::Match($RuleText, '(?s)<WakeupGuideline>(.*?)</WakeupGuideline>')
307
- if (-not $m.Success) { return '' }
308
- return $m.Groups[1].Value.Trim()
309
- }
310
-
311
307
  function Write-ArchGraphRuleBlock {
312
308
  # Merge the frontmatter-stripped ArchGraph rule into a target AGENTS.md
313
309
  # file (DSH home or OpenClaw workspace), replacing the previous ArchGraph
@@ -372,6 +368,149 @@ function Write-OpenClawAgentRule {
372
368
  Write-ArchGraphRuleBlock -DestPath (Join-Path $OpenClawWorkspace 'AGENTS.md') -RuleText $RuleText -Label 'OpenClaw rule'
373
369
  }
374
370
 
371
+ function Convert-SkillFile {
372
+ # Normalize an ArchGraph SKILL.md for the Doubao (desktop agent mode) skill
373
+ # system. Doubao follows the Anthropic Agent Skills spec: the YAML
374
+ # frontmatter may contain ONLY `name` and `description` (see the built-in
375
+ # skill-creator-for-work skill); any other field is rejected. The ArchGraph
376
+ # skills additionally carry VS Code / OpenCode-only fields (argument-hint,
377
+ # disable-model-invocation), so strip the frontmatter down to name +
378
+ # description while keeping the body verbatim and the values byte-intact.
379
+ param(
380
+ [string]$SourceFile,
381
+ [string]$DestinationFile
382
+ )
383
+ $content = Get-Content $SourceFile -Raw -Encoding UTF8
384
+ $m = [regex]::Match($content, '(?s)^---\s*\r?\n(.*?)\r?\n---\s*\r?\n(.*)$')
385
+ if (-not $m.Success) {
386
+ Copy-Item -Force -Path $SourceFile -Destination $DestinationFile
387
+ return
388
+ }
389
+ $front = $m.Groups[1].Value
390
+ $body = $m.Groups[2].Value
391
+
392
+ $name = ''
393
+ $desc = ''
394
+ $nm = [regex]::Match($front, '(?m)^name:\s*(.*)$')
395
+ if ($nm.Success) { $name = $nm.Groups[1].Value.Trim().Trim('"').Trim("'") }
396
+ # Keep the description value verbatim (including any surrounding quotes) so
397
+ # non-ASCII and internal punctuation survive the round-trip byte-for-byte.
398
+ $dm = [regex]::Match($front, '(?m)^description:\s*(.*)$')
399
+ if ($dm.Success) { $desc = $dm.Groups[1].Value.Trim() }
400
+
401
+ $newFront = "---`r`n"
402
+ if ($name) { $newFront += "name: $name`r`n" }
403
+ if ($desc) { $newFront += "description: $desc`r`n" }
404
+ $newFront += "---`r`n"
405
+
406
+ $result = $newFront + "`r`n" + $body
407
+ [System.IO.File]::WriteAllText($DestinationFile, $result, (New-Object System.Text.UTF8Encoding $false))
408
+ }
409
+
410
+ function Copy-DoubaoSkills {
411
+ # Deploy the ArchGraph skills into a Doubao skill root. Doubao adopts the
412
+ # Anthropic Agent Skills layout: one folder per skill containing SKILL.md
413
+ # (plus optional scripts/references/assets). The metadata (name +
414
+ # description) is always in context; the body loads only when the skill
415
+ # triggers. SKILL.md is normalized via Convert-SkillFile; any bundled
416
+ # resources are copied verbatim so the skill folder is self-contained.
417
+ param(
418
+ [string]$Root,
419
+ [string]$SourceSkillsRoot,
420
+ [string[]]$SkillNames,
421
+ [string]$Label
422
+ )
423
+ foreach ($skill in $SkillNames) {
424
+ $src = Join-Path $SourceSkillsRoot $skill
425
+ if (-not (Test-Path (Join-Path $src 'SKILL.md'))) { continue }
426
+ $destDir = Join-Path $Root $skill
427
+ New-Item -ItemType Directory -Force -Path $destDir | Out-Null
428
+ Copy-Item -Recurse -Force -Path (Join-Path $src '*') -Destination $destDir
429
+ Convert-SkillFile -SourceFile (Join-Path $src 'SKILL.md') -DestinationFile (Join-Path $destDir 'SKILL.md')
430
+ }
431
+ Write-Host " argo skills -> $Root ($Label)"
432
+ }
433
+
434
+ function Convert-RuleToSkill {
435
+ # Package the single-source ARGO rule file (argo/rules/archgraph.instructions.md)
436
+ # as a Doubao entry skill. Doubao keeps ONLY a skill's name + description in
437
+ # context permanently (the body loads when the skill triggers) and it
438
+ # forbids angle brackets in the description. So the frontmatter carries a
439
+ # mandatory trigger description (the STEP 0 gate + the "load before any
440
+ # intent-graph read/write" mandate) and the body is the rule text verbatim
441
+ # (frontmatter stripped). This is the closest Doubao allows to an always-on
442
+ # rule: Doubao has no system-prompt / AGENTS.md hook.
443
+ param(
444
+ [string]$RuleFile,
445
+ [string]$DestinationFile
446
+ )
447
+ $rule = Get-Content $RuleFile -Raw -Encoding UTF8
448
+ $body = Get-MarkdownBody -Content $rule
449
+ $description = 'MANDATORY entry skill for the ARGO / ArchGraph intent graph. Whenever the workspace is an ArchGraph workspace (contains design/KG/SystemArchitecture.json) or the argo MCP tools (mcp__argo__*) are available, you MUST load this skill BEFORE reading, querying or writing the architecture graph. STEP 0 gate: identify your Business Actor through the argo MCP and restore your working memory first, then follow this skill for KG-first retrieval, the ARGO red lines, acceptance-test-first work, deduplicated and lossless graph writes, and commit tracing.'
450
+ $front = "---`r`nname: archgraph-argo`r`ndescription: `"$description`"`r`n---`r`n"
451
+ [System.IO.File]::WriteAllText($DestinationFile, ($front + "`r`n" + $body), (New-Object System.Text.UTF8Encoding $false))
452
+ }
453
+
454
+ function Write-DoubaoMcpRecipe {
455
+ # Doubao's desktop agent mode registers MCP servers through its in-app UI
456
+ # (自定义连接器: 传输类型 STDIO / 命令 / 参数 / 环境变量), NOT through a config
457
+ # file, so the installer cannot auto-register. This writes the exact
458
+ # connector values to a reference file and prints a copy-paste recipe.
459
+ #
460
+ # The argo server is a local stdio child with NO fixed workspace: Doubao's
461
+ # MCP client (rmcp) supports the MCP `roots` handshake and passes a
462
+ # `workingDirectory` when spawning a stdio connector, so argo follows the
463
+ # workspace the user selected in Doubao - exactly like the OpenCode (roots)
464
+ # and DeepSeek Harness (injected workspaceRoot) hosts. ARGO_REPO_ROOT is
465
+ # therefore NOT pinned; it remains available as an optional override.
466
+ # graph-mcp (remote Streamable HTTP) is exposed through the same stdio
467
+ # bridge the Cursor deployment uses.
468
+ param(
469
+ [string]$RecipePath,
470
+ [string]$ArgoServer,
471
+ [string]$BridgePath,
472
+ [string]$GraphMcpUrl
473
+ )
474
+ $argoServerSlash = ($ArgoServer -replace '\\', '/')
475
+ $connectors = @(
476
+ [ordered]@{
477
+ serverName = 'argo'
478
+ transport = 'STDIO'
479
+ command = 'node'
480
+ args = @($argoServerSlash)
481
+ }
482
+ )
483
+ if ($BridgePath) {
484
+ $connectors += [ordered]@{
485
+ serverName = 'graph-mcp'
486
+ transport = 'STDIO'
487
+ command = 'node'
488
+ args = @(($BridgePath -replace '\\', '/'))
489
+ env = [ordered]@{ GRAPH_MCP_URL = $GraphMcpUrl }
490
+ }
491
+ }
492
+ $recipe = [ordered]@{
493
+ host = 'Doubao desktop (agent mode) - custom connector (自定义连接器), manual in-app registration'
494
+ note = 'Open Doubao agent mode -> MCP/connector settings -> add a custom connector -> enter the values below (transport 传输类型 = STDIO). Requires Node.js on PATH. Doubao custom connectors run only on the local machine.'
495
+ workspace = 'The argo connector has no fixed workspace: it follows the workspace selected in Doubao (MCP roots / connector workingDirectory), the same way OpenCode (roots) and DeepSeek Harness (injected workspaceRoot) do. To pin a fixed workspace instead, add env ARGO_REPO_ROOT=<absolute project path>.'
496
+ connectors = $connectors
497
+ }
498
+ New-Item -ItemType Directory -Force -Path (Split-Path $RecipePath) | Out-Null
499
+ [System.IO.File]::WriteAllText($RecipePath, ($recipe | ConvertTo-Json -Depth 8), (New-Object System.Text.UTF8Encoding $false))
500
+ Write-Host " Doubao MCP connector recipe -> $RecipePath"
501
+ Write-Host ' Register once in the Doubao app (自定义连接器, 传输类型 STDIO):'
502
+ Write-Host ' 服务器名称: argo'
503
+ Write-Host ' 命令: node'
504
+ Write-Host " 参数: $argoServerSlash"
505
+ Write-Host ' (workspace: follows the workspace selected in Doubao)'
506
+ if ($BridgePath) {
507
+ Write-Host ' 服务器名称: graph-mcp'
508
+ Write-Host ' 命令: node'
509
+ Write-Host " 参数: $($BridgePath -replace '\\', '/')"
510
+ Write-Host " 环境变量: GRAPH_MCP_URL=$GraphMcpUrl"
511
+ }
512
+ }
513
+
375
514
  function Write-OpenClawMcpConfig {
376
515
  # Register the argo MCP server under mcp.servers.argo in
377
516
  # ~/.openclaw/openclaw.json. OpenClaw is a fixed-workspace host: its
@@ -460,6 +599,71 @@ function Write-OpenClawMcpConfig {
460
599
  [System.IO.File]::WriteAllText($configPath, $json, (New-Object System.Text.UTF8Encoding $false))
461
600
  }
462
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
+
463
667
  function Write-DshManagedBlock {
464
668
  # Text-level upsert of a marker-delimited block into a file (used for the
465
669
  # managed rows in ~/.dsh/cordis.patch.yml): replaces the previous managed
@@ -491,200 +695,51 @@ function Write-DshManagedBlock {
491
695
  }
492
696
 
493
697
  function New-DshWakeupPlugin {
494
- # Generate the DSH wakeup-gate Cordis plugin under ~/.dsh/plugins from the
495
- # <WakeupGuideline> block of the rule file. This is the DeepSeek Harness
496
- # equivalent of the OpenCode hook plugin argo/plugins/argo-wakeup.js: it
497
- # registers the gate as the first system-prompt section after the harness
498
- # identity (order -100), before the deployment persona (order 0).
698
+ # Deploy the DSH wakeup-gate Cordis plugin under ~/.dsh/plugins by copying
699
+ # the single-source bundle artifact dsh-argo-wakeup/index.js (generated from
700
+ # the <WakeupGuideline> block of argo/rules/archgraph.instructions.md by
701
+ # scripts/gen-dsh-plugins.js). Copying - not regenerating a template here -
702
+ # keeps the deployed plugin and the shipped bundle byte-identical, so the
703
+ # gate text has exactly one source and cannot fork.
499
704
  param(
500
- [string]$DshHome,
501
- [string]$RuleText
705
+ [string]$DshHome
502
706
  )
503
- $gate = Get-WakeupGuideline -RuleText $RuleText
504
- if (-not $gate) {
505
- Write-Warning ' <WakeupGuideline> not found in the rule file; DSH wakeup plugin skipped.'
707
+ $source = Join-Path (Join-Path $repoRoot 'dsh-argo-wakeup') 'index.js'
708
+ if (-not (Test-Path $source)) {
709
+ Write-Warning " $source not found; DSH wakeup plugin skipped."
506
710
  return $null
507
711
  }
508
712
  $dir = Join-Path (Join-Path $DshHome 'plugins') 'dsh-argo-wakeup'
509
713
  New-Item -ItemType Directory -Force -Path $dir | Out-Null
510
- $gateJson = $gate | ConvertTo-Json
511
- $plugin = @"
512
- // dsh-argo-wakeup - generated by install-argo.ps1 from
513
- // argo/rules/archgraph.instructions.md (single source of truth: the rule file;
514
- // this file is a deployment artifact, do not edit by hand).
515
- // DeepSeek Harness equivalent of the OpenCode hook argo/plugins/argo-wakeup.js:
516
- // registers the unconditional wakeup gate as the first system-prompt section
517
- // after the harness identity, so every session identifies its Business Actor
518
- // through the argo MCP server before responding.
519
- export const name = 'dsh-argo-wakeup'
520
- export const inject = ['systemPrompt']
521
- const WAKEUP_GATE = $gateJson
522
- export function apply(ctx) {
523
- ctx.effect(() => ctx.systemPrompt.section({
524
- name: 'argo:wakeup',
525
- order: -90,
526
- text: WAKEUP_GATE,
527
- }), 'argo:wakeup.section()')
528
- }
529
- "@
530
714
  $indexPath = Join-Path $dir 'index.js'
531
- [System.IO.File]::WriteAllText($indexPath, $plugin, (New-Object System.Text.UTF8Encoding $false))
532
- Write-Host " DSH wakeup plugin generated -> $indexPath"
715
+ Copy-Item -Force -Path $source -Destination $indexPath
716
+ Write-Host " DSH wakeup plugin <- $source (single source)"
533
717
  return $indexPath
534
718
  }
535
719
 
536
720
  function New-DshWorkspaceBridge {
537
- # Generate the DSH workspace bridge plugin under ~/.dsh/plugins. It
538
- # connects directly to the argo MCP server (no dsh-mcp-client row needed)
539
- # with a zero-dependency minimal MCP stdio client, registers every tool as
721
+ # Deploy the DSH workspace bridge plugin under ~/.dsh/plugins by copying the
722
+ # single-source bundle artifact dsh-argo-workspace/index.js. It connects
723
+ # directly to the argo MCP server (no dsh-mcp-client row needed) with a
724
+ # zero-dependency minimal MCP stdio client, registers every tool as
540
725
  # mcp__argo__* and injects the current session's workspace directory
541
726
  # (SessionHeader.cwd) as the per-call `workspaceRoot`, so ONE dsh instance
542
727
  # follows whichever workspace the user switched to - no model-visible
543
- # parameters, no internal tool names, no restart. The argo server honors
544
- # the injected workspaceRoot unconditionally.
728
+ # parameters, no internal tool names, no restart. Copying (not embedding a
729
+ # template) keeps the deployed plugin and the shipped bundle identical.
545
730
  param(
546
731
  [string]$DshHome
547
732
  )
733
+ $source = Join-Path (Join-Path $repoRoot 'dsh-argo-workspace') 'index.js'
734
+ if (-not (Test-Path $source)) {
735
+ Write-Warning " $source not found; DSH workspace bridge skipped."
736
+ return $null
737
+ }
548
738
  $dir = Join-Path (Join-Path $DshHome 'plugins') 'dsh-argo-workspace'
549
739
  New-Item -ItemType Directory -Force -Path $dir | Out-Null
550
- $plugin = @'
551
- // dsh-argo-workspace - generated by install-argo.ps1 (single source of truth:
552
- // the installer; this file is a deployment artifact, do not edit by hand).
553
- //
554
- // Direct MCP bridge to the argo server for DeepSeek Harness. Registers every
555
- // argo tool as mcp__argo__* and injects the current session's workspace
556
- // directory (SessionHeader.cwd) as the per-call `workspaceRoot` argument, so
557
- // one dsh instance follows whichever workspace the user switched to - the
558
- // model sees no extra parameters and no internal tool names. The argo server
559
- // honors the injected workspaceRoot unconditionally.
560
- //
561
- // Zero dependencies on purpose: implements the minimal MCP stdio client
562
- // (JSON-RPC 2.0, one JSON object per line) with Node built-ins only, so the
563
- // plugin runs from any DSH layout without resolving @modelcontextprotocol/sdk.
564
- import { spawn } from 'node:child_process'
565
- import readline from 'node:readline'
566
-
567
- export const name = 'dsh-argo-workspace'
568
- export const inject = ['tools']
569
-
570
- /** Minimal MCP stdio client over the argo server process. */
571
- function createArgoClient(serverPath, env, cwd) {
572
- const child = spawn('node', [serverPath], {
573
- stdio: ['pipe', 'pipe', 'pipe'],
574
- env,
575
- ...(cwd ? { cwd } : {}),
576
- })
577
- const pending = new Map()
578
- let nextId = 1
579
- readline.createInterface({ input: child.stdout }).on('line', (line) => {
580
- let message
581
- try { message = JSON.parse(line) } catch { return }
582
- if (message && typeof message.id === 'number' && pending.has(message.id)) {
583
- const { resolve, reject } = pending.get(message.id)
584
- pending.delete(message.id)
585
- if (message.error) reject(new Error(JSON.stringify(message.error)))
586
- else resolve(message.result)
587
- }
588
- })
589
- child.stderr.on('data', () => {}) // drain; the argo server logs to stderr
590
- const request = (method, params) => new Promise((resolve, reject) => {
591
- const id = nextId++
592
- pending.set(id, { resolve, reject })
593
- child.stdin.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n')
594
- })
595
- return {
596
- request,
597
- notify: (method, params) => {
598
- child.stdin.write(JSON.stringify({ jsonrpc: '2.0', method, params }) + '\n')
599
- },
600
- close: () => child.kill(),
601
- }
602
- }
603
-
604
- /** Connect, list tools, register them, and keep the client until disposal. */
605
- export async function apply(ctx, config) {
606
- const serverPath = config.serverPath
607
- const workspaces = Array.isArray(config.workspaces) ? config.workspaces : []
608
- const env = { ...process.env }
609
- if (workspaces.length > 0) env.ARGO_WORKSPACE_ROOTS = workspaces.join(';')
610
- const client = createArgoClient(serverPath, env, config.cwd)
611
- ctx.effect(() => () => client.close(), 'dsh-argo-workspace.dispose()')
612
-
613
- let tools = []
614
- try {
615
- await client.request('initialize', {
616
- protocolVersion: '2024-11-05',
617
- capabilities: {},
618
- clientInfo: { name: 'dsh-argo-workspace', version: '0.1.0' },
619
- })
620
- client.notify('notifications/initialized', {})
621
- const listed = await client.request('tools/list', {})
622
- tools = (listed && Array.isArray(listed.tools)) ? listed.tools : []
623
- } catch (error) {
624
- console.warn(`[dsh-argo-workspace] failed to connect to the argo MCP server (${serverPath}): ${error && error.message ? error.message : error}`)
625
- return
626
- }
627
-
628
- const disposers = []
629
- for (const tool of tools) {
630
- const publicName = 'mcp__argo__' + tool.name
631
- disposers.push(ctx.tools.register({
632
- name: publicName,
633
- description: tool.description ?? '',
634
- parameters: tool.inputSchema,
635
- output: {
636
- schema: {
637
- type: 'object',
638
- properties: {
639
- content: { type: 'array', items: {} },
640
- structuredContent: {},
641
- },
642
- required: ['content'],
643
- additionalProperties: false,
644
- },
645
- render: (_args, value) => value.content,
646
- },
647
- execute: async (args, exec) => {
648
- // SessionHeader.cwd (the durable session workspace), NOT
649
- // requestHeader() — that returns the request EpochHeader
650
- // (config/system/tools) which has no cwd field.
651
- const sessionCwd = exec && exec.agent && exec.agent.session
652
- ? exec.agent.session.header?.cwd
653
- : undefined
654
- const injected = { ...(args && typeof args === 'object' ? args : {}) }
655
- if (typeof sessionCwd === 'string' && sessionCwd !== '') {
656
- injected.workspaceRoot = sessionCwd
657
- }
658
- if (exec && exec.signal && exec.signal.aborted) {
659
- throw new Error('aborted')
660
- }
661
- const result = await client.request('tools/call', {
662
- name: tool.name,
663
- arguments: injected,
664
- })
665
- const text = Array.isArray(result.content)
666
- ? result.content
667
- .map((block) => block && block.type === 'text' && typeof block.text === 'string'
668
- ? block.text
669
- : JSON.stringify(block))
670
- .join('\n')
671
- : (result.toolResult !== undefined ? JSON.stringify(result.toolResult) : '(no output)')
672
- if (result.isError === true) throw new Error(text)
673
- return {
674
- content: [{ type: 'text', text }],
675
- ...(result.structuredContent !== undefined ? { structuredContent: result.structuredContent } : {}),
676
- }
677
- },
678
- }))
679
- }
680
- ctx.effect(() => () => {
681
- for (const dispose of disposers) dispose()
682
- }, 'dsh-argo-workspace.tools')
683
- }
684
- '@
685
740
  $indexPath = Join-Path $dir 'index.js'
686
- [System.IO.File]::WriteAllText($indexPath, $plugin, (New-Object System.Text.UTF8Encoding $false))
687
- Write-Host " DSH workspace bridge generated -> $indexPath"
741
+ Copy-Item -Force -Path $source -Destination $indexPath
742
+ Write-Host " DSH workspace bridge <- $source (single source)"
688
743
  return $indexPath
689
744
  }
690
745
 
@@ -789,10 +844,9 @@ Write-Host "[4/22] argo\skills\argo-init -> $skillDest"
789
844
  Copy-Tree -Source $skillSrc -Destination $skillDest
790
845
  $reconcileSkillSrc = Join-Path (Join-Path $argoDir 'skills') 'ea-human-reconcile'
791
846
  $diagSkillSrc = Join-Path (Join-Path $argoDir 'skills') 'agent-search-diagnosis'
792
- 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)"
793
848
  Copy-Tree -Source $reconcileSkillSrc -Destination (Join-Path $SkillsRoot 'ea-human-reconcile')
794
- Write-Host ' argo\skills\agent-search-diagnosis -> $SkillsRoot\agent-search-diagnosis (agent search diagnosis skill)'
795
- 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.
796
850
 
797
851
  $ruleSrc = Join-Path (Join-Path $argoDir 'rules') 'archgraph.instructions.md'
798
852
  $ruleDest = Join-Path $PromptsRoot 'archgraph.instructions.md'
@@ -808,10 +862,9 @@ Copy-Item -Force -Path $depsSrc -Destination $depsDest
808
862
  $cursorSkillDest = Join-Path $CursorSkillsRoot 'argo-init'
809
863
  Write-Host "[7/22] argo\skills\argo-init -> $cursorSkillDest (Cursor)"
810
864
  Copy-Tree -Source $skillSrc -Destination $cursorSkillDest
811
- 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)"
812
866
  Copy-Tree -Source $reconcileSkillSrc -Destination (Join-Path $CursorSkillsRoot 'ea-human-reconcile')
813
- Write-Host ' argo\skills\agent-search-diagnosis -> $CursorSkillsRoot\agent-search-diagnosis (Cursor)'
814
- 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.
815
868
 
816
869
  $mcpBridgeSrc = Join-Path $argoDir 'mcp-bridges'
817
870
  $mcpBridgeDest = Join-Path $CursorMcpBridgesRoot ''
@@ -821,9 +874,9 @@ Copy-Tree -Source $mcpBridgeSrc -Destination $mcpBridgeDest
821
874
  $openCodeSkillDest = Join-Path $OpenCodeSkillsRoot 'argo-init'
822
875
  Write-Host "[8/22] argo\skills\argo-init -> $openCodeSkillDest (OpenCode)"
823
876
  Copy-Tree -Source $skillSrc -Destination $openCodeSkillDest
824
- 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)"
825
878
  Copy-Tree -Source $reconcileSkillSrc -Destination (Join-Path $OpenCodeSkillsRoot 'ea-human-reconcile')
826
- 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)"
827
880
  Copy-Tree -Source $diagSkillSrc -Destination (Join-Path $OpenCodeSkillsRoot 'agent-search-diagnosis')
828
881
 
829
882
  Write-Host "[9/22] argo\rules\archgraph.instructions.md -> $OpenCodeAgentsPath (OpenCode global AGENTS.md)"
@@ -862,15 +915,14 @@ if ($SkipDsh) {
862
915
 
863
916
  Write-Host "[16/22] argo\skills\argo-init -> $dshSkillDest (DeepSeek Harness skill)"
864
917
  Copy-Tree -Source (Join-Path $argoDir 'skills\argo-init') -Destination $dshSkillDest
865
- 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)"
866
919
  Copy-Tree -Source (Join-Path $argoDir 'skills\ea-human-reconcile') -Destination (Join-Path (Join-Path $DshHome 'skills') 'ea-human-reconcile')
867
- Write-Host ' argo\skills\agent-search-diagnosis -> $DshHome\skills\agent-search-diagnosis (DeepSeek Harness skill)'
868
- 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.
869
921
 
870
- Write-Host "[17/22] argo\rules\<WakeupGuideline> -> $DshHome\plugins\dsh-argo-wakeup\index.js (DeepSeek Harness wakeup plugin)"
871
- $wakeupDshPath = New-DshWakeupPlugin -DshHome $DshHome -RuleText $ruleSrcContent
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)"
923
+ $wakeupDshPath = New-DshWakeupPlugin -DshHome $DshHome
872
924
 
873
- 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)"
874
926
  # The generated dsh-argo-workspace bridge connects directly to the argo
875
927
  # server (no dsh-mcp-client row), registers every tool as mcp__argo__* and
876
928
  # injects the current session's workspace (SessionHeader.cwd) as the
@@ -878,9 +930,20 @@ if ($SkipDsh) {
878
930
  # user switched to. The server honors the injected workspaceRoot
879
931
  # unconditionally.
880
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('\', '/')
881
939
  if ($bridgeDshPath) {
882
940
  $bridgeUrl = 'file:///' + (($bridgeDshPath -replace '\\', '/').TrimStart('/'))
883
- $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
884
947
  if ($DshWorkspaces) {
885
948
  $wsList = @($DshWorkspaces.Replace('\', '/').Split(';') | ForEach-Object { " - $_" }) -join "`n"
886
949
  $bridgeConfig += " workspaces:`n" + $wsList + "`n"
@@ -904,10 +967,8 @@ if ($SkipDsh) {
904
967
  Write-Host "[19/22] argo\agents -> $DshHome\.agent-presets\<id> (DeepSeek Harness agent presets)"
905
968
  New-DshAgentPresets -DshHome $DshHome -AgentsSrc (Join-Path $argoDir 'agents')
906
969
 
907
- Write-Host " note: graph-mcp remote ($GraphMcpUrl) is NOT registered for DeepSeek Harness -"
908
- Write-Host " ~/.dsh/cordis.patch.yml only carries plugin rows (the dsh-argo-workspace bridge spawns"
909
- Write-Host " the local argo server); an HTTP remote MCP server has no equivalent cordis row here."
910
- 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;"
911
972
  Write-Host ' new sessions pick up the global rule and the argo-init skill automatically.'
912
973
  Write-Host ' The argo tools (mcp__argo__*) auto-follow the workspace the user switched to'
913
974
  Write-Host ' in one dsh instance - no restart needed between workspaces (the server honors'
@@ -927,16 +988,58 @@ if ($SkipOpenClaw) {
927
988
 
928
989
  Write-Host "[21/22] argo\skills\argo-init -> $openClawSkillDest (OpenClaw managed skill, all agents)"
929
990
  Copy-Tree -Source (Join-Path $argoDir 'skills\argo-init') -Destination $openClawSkillDest
930
- 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)"
931
992
  Copy-Tree -Source (Join-Path $argoDir 'skills\ea-human-reconcile') -Destination (Join-Path (Join-Path $OpenClawHome 'skills') 'ea-human-reconcile')
932
- Write-Host ' argo\skills\agent-search-diagnosis -> $OpenClawHome\skills\agent-search-diagnosis (OpenClaw managed skill, all agents)'
933
- 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.
934
994
 
935
995
  Write-Host ' OpenClaw injects AGENTS.md into Project Context on every session, so the wakeup'
936
996
  Write-Host ' gate (UNCONDITIONAL STARTUP GATE) is active on the next OpenClaw session; restart'
937
997
  Write-Host ' the OpenClaw gateway (openclaw gateway restart) if it is already running.'
938
998
  }
939
999
 
1000
+ if ($SkipDoubao) {
1001
+ Write-Host 'Skipped Doubao integration (-SkipDoubao).'
1002
+ } else {
1003
+ # Doubao (desktop agent mode) skill discovery. Doubao adopts the Anthropic
1004
+ # Agent Skills spec and loads user skills from
1005
+ # <workspace>/.user_skills/<name>/SKILL.md (per-session workspace root)
1006
+ # plus a global user root
1007
+ # ~/Doubao/skills/<name>/SKILL.md
1008
+ # Doubao exposes no AGENTS.md-equivalent always-on instruction file, so the
1009
+ # WakeupGuideline / unconditional startup gate has no hook (skills are the
1010
+ # fully ported surface). MCP servers are registered through Doubao's in-app
1011
+ # custom connector UI (STDIO), not a config file, so the installer emits a
1012
+ # copy-paste connector recipe instead of writing a config.
1013
+ # agent-search-diagnosis is OpenCode-only (it assumes `opencode` session export); not deployed to Doubao.
1014
+ $doubaoSkills = @('argo-init', 'ea-human-reconcile')
1015
+ $doubaoSkillsSrc = Join-Path $argoDir 'skills'
1016
+ Write-Host '==> Deploying Doubao (desktop agent mode) integration'
1017
+ Copy-DoubaoSkills -Root (Join-Path $DoubaoWorkspace '.user_skills') -SourceSkillsRoot $doubaoSkillsSrc -SkillNames $doubaoSkills -Label 'workspace .user_skills'
1018
+ Copy-DoubaoSkills -Root (Join-Path $DoubaoHome 'skills') -SourceSkillsRoot $doubaoSkillsSrc -SkillNames $doubaoSkills -Label 'global ~/Doubao/skills'
1019
+ # Entry skill: the single-source ARGO rules packaged as a Doubao skill whose
1020
+ # always-in-context description states the STEP 0 gate and the mandate to
1021
+ # load it before any intent-graph read/write. This is the closest Doubao
1022
+ # allows to an always-on rule (skills have no system-prompt hook).
1023
+ $doubaoRule = Join-Path $argoDir 'rules\archgraph.instructions.md'
1024
+ foreach ($doubaoRoot in @((Join-Path $DoubaoWorkspace '.user_skills'), (Join-Path $DoubaoHome 'skills'))) {
1025
+ $entryDir = Join-Path $doubaoRoot 'archgraph-argo'
1026
+ New-Item -ItemType Directory -Force -Path $entryDir | Out-Null
1027
+ Convert-RuleToSkill -RuleFile $doubaoRule -DestinationFile (Join-Path $entryDir 'SKILL.md')
1028
+ Write-Host " entry skill (ARGO rules) -> $(Join-Path $entryDir 'SKILL.md')"
1029
+ }
1030
+ Write-Host ' Doubao injects each skill''s name + description into context and loads the'
1031
+ Write-Host ' body on trigger; new skills are picked up on the next agent session.'
1032
+ $doubaoBridgeDir = Join-Path $ArgoRoot 'mcp-bridges'
1033
+ Copy-Tree -Source (Join-Path $argoDir 'mcp-bridges') -Destination $doubaoBridgeDir
1034
+ $doubaoBridgePath = Join-Path $doubaoBridgeDir 'graph-mcp-stdio.js'
1035
+ if (-not (Test-Path $doubaoBridgePath)) { $doubaoBridgePath = '' }
1036
+ Write-DoubaoMcpRecipe -RecipePath (Join-Path $ArgoRoot 'doubao-mcp.json') -ArgoServer (Join-Path $ArgoRoot 'scripts\argo-mcp-server.js') -BridgePath $doubaoBridgePath -GraphMcpUrl $GraphMcpUrl
1037
+ Write-Host ' the argo connector has no fixed workspace: it follows the workspace you'
1038
+ Write-Host ' select in Doubao (MCP roots / connector workingDirectory), like OpenCode/DSH.'
1039
+ Write-Host ' note: Doubao has no AGENTS.md-equivalent always-on rule file, so the ARGO'
1040
+ Write-Host ' WakeupGuideline / unconditional startup gate is not deployed (no hook).'
1041
+ }
1042
+
940
1043
  if ($SkipDeps) {
941
1044
  Write-Host 'Skipped dependency install (-SkipDeps).'
942
1045
  } elseif (Get-Command npm -ErrorAction SilentlyContinue) {
@@ -1113,6 +1216,33 @@ if ($SkipMcp) {
1113
1216
  }
1114
1217
  }
1115
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
+
1116
1246
  $wakeupPluginPath = Join-Path $PluginsRoot 'argo-wakeup.js'
1117
1247
  if (Test-Path $wakeupPluginPath) {
1118
1248
  Write-Host '==> Registering argo-wakeup plugin in OpenCode'