archgraph-argo 0.9.6 → 0.9.8

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.
@@ -18,6 +18,7 @@ const {
18
18
  getWorkspaceRoot,
19
19
  hasStaticWorkspace,
20
20
  resolveArgoPath,
21
+ resolveCallWorkspaceRoot,
21
22
  setMcpWorkspaceRoots,
22
23
  } = require('./argo-paths.js');
23
24
  const canonicalSemanticInitStorage = new AsyncLocalStorage();
@@ -304,6 +305,25 @@ const TOOLS = [
304
305
  },
305
306
  ];
306
307
 
308
+ // Every tool accepts an optional per-call `workspaceRoot` (absolute path,
309
+ // honored only when listed in ARGO_WORKSPACE_ROOTS). The DeepSeek Harness
310
+ // bridge injects the current session's workspace directory here automatically.
311
+ // (tools/list dedupes with systemArchitectureMcp/validatorMcp tools, which
312
+ // carry the same field; this covers the local-only rows like initializeWorkspace.)
313
+ const WORKSPACE_ROOT_PARAM = Object.freeze({
314
+ type: 'string',
315
+ description:
316
+ 'Optional absolute workspace root for this call. Honored only when listed in ARGO_WORKSPACE_ROOTS; otherwise the server launch directory is used.',
317
+ });
318
+ for (const tool of TOOLS) {
319
+ const inputSchema = tool && tool.inputSchema;
320
+ if (inputSchema && inputSchema.type === 'object' && inputSchema.properties) {
321
+ if (!Object.prototype.hasOwnProperty.call(inputSchema.properties, 'workspaceRoot')) {
322
+ inputSchema.properties.workspaceRoot = WORKSPACE_ROOT_PARAM;
323
+ }
324
+ }
325
+ }
326
+
307
327
  function intentElementContextInputSchema() {
308
328
  return {
309
329
  type: 'object',
@@ -374,14 +394,20 @@ function resolveWorkspaceRoot() {
374
394
  return getWorkspaceRoot();
375
395
  }
376
396
 
397
+ function resolveWorkspaceRoot(args) {
398
+ // Per-call workspaceRoot override (honored only when in the
399
+ // ARGO_WORKSPACE_ROOTS allowlist); defaults to the launch-directory root.
400
+ return resolveCallWorkspaceRoot(args);
401
+ }
402
+
377
403
  async function callTool(name, args = {}, progressToken = null, dependencies = undefined) {
378
- loadRepositoryArgoEnvironment(resolveWorkspaceRoot());
404
+ loadRepositoryArgoEnvironment(resolveWorkspaceRoot(args));
379
405
  if (name === 'initializeWorkspace') {
380
- const workspace = await initializeWorkspace(resolveWorkspaceRoot());
406
+ const workspace = await initializeWorkspace(resolveWorkspaceRoot(args));
381
407
  const composition = canonicalSemanticInitStorage.getStore()
382
408
  || systemArchitectureMcp.createDefaultCanonicalSemanticInitComposition();
383
409
  const semanticLifecycle = await runCanonicalSemanticInit(composition, {
384
- repositoryRoot: resolveWorkspaceRoot(),
410
+ repositoryRoot: resolveWorkspaceRoot(args),
385
411
  workspace,
386
412
  });
387
413
  return toolResult({
@@ -121,6 +121,48 @@ function normalizeRelativePath(value) {
121
121
  return String(value == null ? '' : value).replace(/\\/g, '/').replace(/^\/+/, '');
122
122
  }
123
123
 
124
+ /**
125
+ * Resolve the workspace root for one tool call.
126
+ *
127
+ * Defaults to the launch-directory root (`getWorkspaceRoot()`). A per-call
128
+ * `workspaceRoot` argument (absolute path) is honored only when it appears in
129
+ * the `ARGO_WORKSPACE_ROOTS` allowlist (semicolon or comma separated absolute
130
+ * paths). Without an allowlist the override is ignored, so existing
131
+ * deployments keep launch-directory resolution with zero behavior change.
132
+ * This powers the DeepSeek Harness bridge, which injects the current session's
133
+ * workspace directory into every call so one dsh instance can follow the
134
+ * workspace the user switched to.
135
+ *
136
+ * @param {object} [args] - tool call arguments (may carry `workspaceRoot`).
137
+ * @returns {string} resolved absolute workspace root.
138
+ * @throws when the requested root is not in the allowlist.
139
+ */
140
+ function resolveCallWorkspaceRoot(args = {}) {
141
+ const requested =
142
+ typeof args === 'object' && args !== null && typeof args.workspaceRoot === 'string'
143
+ ? String(args.workspaceRoot).trim()
144
+ : '';
145
+ if (requested === '') {
146
+ return getWorkspaceRoot();
147
+ }
148
+ const allowlist = (process.env.ARGO_WORKSPACE_ROOTS || '')
149
+ .split(/[;,]/)
150
+ .map((entry) => entry.trim())
151
+ .filter(Boolean)
152
+ .map((entry) => path.resolve(entry));
153
+ if (allowlist.length === 0) {
154
+ return getWorkspaceRoot();
155
+ }
156
+ const resolved = path.resolve(requested);
157
+ const allowed = allowlist.some(
158
+ (root) => resolved === root || resolved.startsWith(root + path.sep),
159
+ );
160
+ if (!allowed) {
161
+ throw new Error(`workspaceRoot not in ARGO_WORKSPACE_ROOTS allowlist: ${requested}`);
162
+ }
163
+ return resolved;
164
+ }
165
+
124
166
  module.exports = {
125
167
  getArgoRoot,
126
168
  getArgoEnvPath,
@@ -128,6 +170,7 @@ module.exports = {
128
170
  hasStaticWorkspace,
129
171
  normalizeRelativePath,
130
172
  resolveArgoPath,
173
+ resolveCallWorkspaceRoot,
131
174
  resolveWorkspacePath,
132
175
  setMcpWorkspaceRoots,
133
176
  };
@@ -5,7 +5,7 @@ const crypto = require('node:crypto');
5
5
 
6
6
  const {
7
7
  getArgoRoot,
8
- getWorkspaceRoot,
8
+ resolveCallWorkspaceRoot,
9
9
  } = require('./argo-paths.js');
10
10
 
11
11
  const DEFAULT_GRAPH_PATH = 'design/KG/SystemArchitecture.json';
@@ -381,6 +381,23 @@ const TOOLS = [
381
381
  },
382
382
  ];
383
383
 
384
+ // Every tool accepts an optional per-call `workspaceRoot` (absolute path,
385
+ // honored only when listed in ARGO_WORKSPACE_ROOTS). The DeepSeek Harness
386
+ // bridge injects the current session's workspace directory here automatically.
387
+ const WORKSPACE_ROOT_PARAM = Object.freeze({
388
+ type: 'string',
389
+ description:
390
+ 'Optional absolute workspace root for this call. Honored only when listed in ARGO_WORKSPACE_ROOTS; otherwise the server launch directory is used.',
391
+ });
392
+ for (const tool of TOOLS) {
393
+ const inputSchema = tool && tool.inputSchema;
394
+ if (inputSchema && inputSchema.type === 'object' && inputSchema.properties) {
395
+ if (!Object.prototype.hasOwnProperty.call(inputSchema.properties, 'workspaceRoot')) {
396
+ inputSchema.properties.workspaceRoot = WORKSPACE_ROOT_PARAM;
397
+ }
398
+ }
399
+ }
400
+
384
401
  function intentElementContextInputSchema() {
385
402
  return {
386
403
  type: 'object',
@@ -448,13 +465,15 @@ function mutationInputSchema() {
448
465
  };
449
466
  }
450
467
 
451
- function resolveWorkspaceRoot() {
452
- return getWorkspaceRoot();
468
+ function resolveWorkspaceRoot(args) {
469
+ // Per-call workspaceRoot override (honored only when in the
470
+ // ARGO_WORKSPACE_ROOTS allowlist); defaults to the launch-directory root.
471
+ return resolveCallWorkspaceRoot(args);
453
472
  }
454
473
 
455
474
  function initializeWorkspace(request) {
456
475
  return require('./argo-mcp-server.js').initializeWorkspace(
457
- request && request.repositoryRoot ? request.repositoryRoot : resolveWorkspaceRoot(),
476
+ request && request.repositoryRoot ? request.repositoryRoot : resolveWorkspaceRoot(request),
458
477
  );
459
478
  }
460
479
 
@@ -508,7 +527,7 @@ function readJson(filePath, label) {
508
527
  }
509
528
 
510
529
  async function loadContext(args = {}) {
511
- const workspaceRoot = resolveWorkspaceRoot();
530
+ const workspaceRoot = resolveWorkspaceRoot(args);
512
531
  const graphPath = resolveWorkspacePath(workspaceRoot, args.architecturePath || DEFAULT_GRAPH_PATH);
513
532
  const schemaPath = resolveSchemaPath(workspaceRoot);
514
533
  const context = {
@@ -9,6 +9,7 @@ const execFileAsync = promisify(execFile);
9
9
  const {
10
10
  getArgoRoot,
11
11
  getWorkspaceRoot,
12
+ resolveCallWorkspaceRoot,
12
13
  } = require('./argo-paths.js');
13
14
 
14
15
  const HANDOFF_STAGES = ['intent-to-implementation', 'implementation-to-coding'];
@@ -85,8 +86,27 @@ const TOOLS = [
85
86
  },
86
87
  ];
87
88
 
88
- function resolveWorkspaceRoot() {
89
- return getWorkspaceRoot();
89
+ // Every tool accepts an optional per-call `workspaceRoot` (absolute path,
90
+ // honored only when listed in ARGO_WORKSPACE_ROOTS). The DeepSeek Harness
91
+ // bridge injects the current session's workspace directory here automatically.
92
+ const WORKSPACE_ROOT_PARAM = Object.freeze({
93
+ type: 'string',
94
+ description:
95
+ 'Optional absolute workspace root for this call. Honored only when listed in ARGO_WORKSPACE_ROOTS; otherwise the server launch directory is used.',
96
+ });
97
+ for (const tool of TOOLS) {
98
+ const inputSchema = tool && tool.inputSchema;
99
+ if (inputSchema && inputSchema.type === 'object' && inputSchema.properties) {
100
+ if (!Object.prototype.hasOwnProperty.call(inputSchema.properties, 'workspaceRoot')) {
101
+ inputSchema.properties.workspaceRoot = WORKSPACE_ROOT_PARAM;
102
+ }
103
+ }
104
+ }
105
+
106
+ function resolveWorkspaceRoot(args) {
107
+ // Per-call workspaceRoot override (honored only when in the
108
+ // ARGO_WORKSPACE_ROOTS allowlist); defaults to the launch-directory root.
109
+ return resolveCallWorkspaceRoot(args);
90
110
  }
91
111
 
92
112
  function resolveScriptPath(workspaceRoot, candidates) {
@@ -231,7 +251,7 @@ function toolResult(payload) {
231
251
  }
232
252
 
233
253
  async function callTool(name, args, progressToken = null) {
234
- const workspaceRoot = resolveWorkspaceRoot();
254
+ const workspaceRoot = resolveWorkspaceRoot(args);
235
255
 
236
256
  if (name === 'validateSystemArchitecture') {
237
257
  return toolResult(await runValidatorScript(workspaceRoot, 'validateSystemArchitecture'));
package/install-argo.ps1 CHANGED
@@ -18,6 +18,8 @@ param(
18
18
  [switch]$SkipMcp,
19
19
  [switch]$SkipDsh,
20
20
  [string]$DshHome = "$env:USERPROFILE\.dsh",
21
+ [string]$DshCwd = '',
22
+ [string]$DshWorkspaces = '',
21
23
  [string]$McpPath
22
24
  )
23
25
 
@@ -264,44 +266,25 @@ function Get-WakeupGuideline {
264
266
  }
265
267
 
266
268
  function Write-DshAgentRule {
267
- # Merge the frontmatter-stripped ArchGraph rule (plus a DSH adapter note
268
- # explaining the mcp__argo__ tool prefix) into ~/.dsh/AGENTS.md, replacing
269
- # the previous ArchGraph block while preserving surrounding user content.
269
+ # Merge the frontmatter-stripped ArchGraph rule into ~/.dsh/AGENTS.md,
270
+ # replacing the previous ArchGraph block while preserving surrounding user
271
+ # content. The rule body is injected verbatim - no adapter prose, so the
272
+ # working prompt stays identical to the Copilot / Cursor / OpenCode rules.
270
273
  param(
271
274
  [string]$DshHome,
272
275
  [string]$RuleText
273
276
  )
274
- $adapterNote = @'
275
- # ArchGraph ARGO Workflow Rules (DeepSeek Harness edition)
276
-
277
- > Deployed by install-argo.ps1 from argo/rules/archgraph.instructions.md
278
- > (single source of truth; this file is a deployment artifact).
279
- > DeepSeek Harness injects ~/.dsh/AGENTS.md through dsh-agent-instructions as
280
- > user-global instructions; the file is injected verbatim, so the YAML
281
- > frontmatter of the source rule is stripped here.
282
-
283
- ## DSH adapter note
284
- - The ARGO MCP server is registered under serverName "argo" through the
285
- @deepseek-ai/dsh-mcp-client plugin; its tools are exposed to the model with
286
- the mcp__argo__ prefix (e.g. mcp__argo__getSystemArchitecture). When a rule
287
- below names a tool like getSystemArchitecture, call the mcp__argo__* form.
288
- - The argo-init skill is installed at ~/.dsh/skills/argo-init/SKILL.md and is
289
- loadable through the harness skill tool.
290
- '@
291
- $body = Get-MarkdownBody -Content $RuleText
292
- $ruleContent = $adapterNote + "`n`n" + $body
277
+ $ruleContent = Get-MarkdownBody -Content $RuleText
293
278
  $dest = Join-Path $DshHome 'AGENTS.md'
294
- $marker = 'ArchGraph ARGO Workflow Rules'
279
+ $marker = '<WakeupGuideline>'
295
280
  New-Item -ItemType Directory -Force -Path $DshHome | Out-Null
296
281
  if (Test-Path $dest) {
297
282
  $existing = Get-Content $dest -Raw -Encoding UTF8
298
283
  if ($existing -like "*$marker*") {
299
284
  $endTag = '</ToolsGuideline>'
300
- $markerIdx = $existing.IndexOf($marker)
301
- if ($markerIdx -lt 0) { $markerIdx = 0 }
302
- $startIdx = $existing.LastIndexOf('# ArchGraph', $markerIdx)
285
+ $startIdx = $existing.IndexOf($marker)
303
286
  if ($startIdx -lt 0) { $startIdx = 0 }
304
- $endIdx = $existing.IndexOf($endTag, $markerIdx)
287
+ $endIdx = $existing.IndexOf($endTag, $startIdx)
305
288
  $before = $existing.Substring(0, $startIdx).TrimEnd()
306
289
  if ($endIdx -lt 0) {
307
290
  $combined = $before
@@ -398,6 +381,156 @@ export function apply(ctx) {
398
381
  return $indexPath
399
382
  }
400
383
 
384
+ function New-DshWorkspaceBridge {
385
+ # Generate the DSH workspace bridge plugin under ~/.dsh/plugins. It
386
+ # connects directly to the argo MCP server (no dsh-mcp-client row needed)
387
+ # with a zero-dependency minimal MCP stdio client, registers every tool as
388
+ # mcp__argo__* and injects the current session's workspace directory
389
+ # (SessionHeader.cwd) as the per-call `workspaceRoot`, so ONE dsh instance
390
+ # follows whichever workspace the user switched to - no model-visible
391
+ # parameters, no internal tool names, no restart. The argo server honors
392
+ # workspaceRoot only when listed in ARGO_WORKSPACE_ROOTS (install-argo.ps1
393
+ # -DshWorkspaces).
394
+ param(
395
+ [string]$DshHome
396
+ )
397
+ $dir = Join-Path (Join-Path $DshHome 'plugins') 'dsh-argo-workspace'
398
+ New-Item -ItemType Directory -Force -Path $dir | Out-Null
399
+ $plugin = @'
400
+ // dsh-argo-workspace - generated by install-argo.ps1 (single source of truth:
401
+ // the installer; this file is a deployment artifact, do not edit by hand).
402
+ //
403
+ // Direct MCP bridge to the argo server for DeepSeek Harness. Registers every
404
+ // argo tool as mcp__argo__* and injects the current session's workspace
405
+ // directory (SessionHeader.cwd) as the per-call `workspaceRoot` argument, so
406
+ // one dsh instance follows whichever workspace the user switched to - the
407
+ // model sees no extra parameters and no internal tool names. The argo server
408
+ // honors workspaceRoot only when the workspace is listed in its
409
+ // ARGO_WORKSPACE_ROOTS allowlist.
410
+ //
411
+ // Zero dependencies on purpose: implements the minimal MCP stdio client
412
+ // (JSON-RPC 2.0, one JSON object per line) with Node built-ins only, so the
413
+ // plugin runs from any DSH layout without resolving @modelcontextprotocol/sdk.
414
+ import { spawn } from 'node:child_process'
415
+ import readline from 'node:readline'
416
+
417
+ export const name = 'dsh-argo-workspace'
418
+ export const inject = ['tools']
419
+
420
+ /** Minimal MCP stdio client over the argo server process. */
421
+ function createArgoClient(serverPath, env, cwd) {
422
+ const child = spawn('node', [serverPath], {
423
+ stdio: ['pipe', 'pipe', 'pipe'],
424
+ env,
425
+ ...(cwd ? { cwd } : {}),
426
+ })
427
+ const pending = new Map()
428
+ let nextId = 1
429
+ readline.createInterface({ input: child.stdout }).on('line', (line) => {
430
+ let message
431
+ try { message = JSON.parse(line) } catch { return }
432
+ if (message && typeof message.id === 'number' && pending.has(message.id)) {
433
+ const { resolve, reject } = pending.get(message.id)
434
+ pending.delete(message.id)
435
+ if (message.error) reject(new Error(JSON.stringify(message.error)))
436
+ else resolve(message.result)
437
+ }
438
+ })
439
+ child.stderr.on('data', () => {}) // drain; the argo server logs to stderr
440
+ const request = (method, params) => new Promise((resolve, reject) => {
441
+ const id = nextId++
442
+ pending.set(id, { resolve, reject })
443
+ child.stdin.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n')
444
+ })
445
+ return {
446
+ request,
447
+ notify: (method, params) => {
448
+ child.stdin.write(JSON.stringify({ jsonrpc: '2.0', method, params }) + '\n')
449
+ },
450
+ close: () => child.kill(),
451
+ }
452
+ }
453
+
454
+ /** Connect, list tools, register them, and keep the client until disposal. */
455
+ export async function apply(ctx, config) {
456
+ const serverPath = config.serverPath
457
+ const workspaces = Array.isArray(config.workspaces) ? config.workspaces : []
458
+ const env = { ...process.env }
459
+ if (workspaces.length > 0) env.ARGO_WORKSPACE_ROOTS = workspaces.join(';')
460
+ const client = createArgoClient(serverPath, env, config.cwd)
461
+ ctx.effect(() => () => client.close(), 'dsh-argo-workspace.dispose()')
462
+
463
+ let tools = []
464
+ try {
465
+ await client.request('initialize', {
466
+ protocolVersion: '2024-11-05',
467
+ capabilities: {},
468
+ clientInfo: { name: 'dsh-argo-workspace', version: '0.1.0' },
469
+ })
470
+ client.notify('notifications/initialized', {})
471
+ const listed = await client.request('tools/list', {})
472
+ tools = (listed && Array.isArray(listed.tools)) ? listed.tools : []
473
+ } catch (error) {
474
+ console.warn(`[dsh-argo-workspace] failed to connect to the argo MCP server (${serverPath}): ${error && error.message ? error.message : error}`)
475
+ return
476
+ }
477
+
478
+ const disposers = []
479
+ for (const tool of tools) {
480
+ const publicName = 'mcp__argo__' + tool.name
481
+ disposers.push(ctx.tools.register({
482
+ name: publicName,
483
+ description: tool.description ?? '',
484
+ parameters: tool.inputSchema,
485
+ output: {
486
+ schema: {
487
+ type: 'object',
488
+ properties: { content: { type: 'array', items: {} } },
489
+ required: ['content'],
490
+ additionalProperties: false,
491
+ },
492
+ },
493
+ execute: async (args, exec) => {
494
+ const sessionCwd = exec && exec.agent && exec.agent.session
495
+ ? exec.agent.session.requestHeader().cwd
496
+ : undefined
497
+ const injected = { ...(args && typeof args === 'object' ? args : {}) }
498
+ if (typeof sessionCwd === 'string' && sessionCwd !== '') {
499
+ injected.workspaceRoot = sessionCwd
500
+ }
501
+ if (exec && exec.signal && exec.signal.aborted) {
502
+ throw new Error('aborted')
503
+ }
504
+ const result = await client.request('tools/call', {
505
+ name: tool.name,
506
+ arguments: injected,
507
+ })
508
+ const text = Array.isArray(result.content)
509
+ ? result.content
510
+ .map((block) => block && block.type === 'text' && typeof block.text === 'string'
511
+ ? block.text
512
+ : JSON.stringify(block))
513
+ .join('\n')
514
+ : (result.toolResult !== undefined ? JSON.stringify(result.toolResult) : '(no output)')
515
+ if (result.isError === true) throw new Error(text)
516
+ return {
517
+ content: [{ type: 'text', text }],
518
+ ...(result.structuredContent !== undefined ? { structuredContent: result.structuredContent } : {}),
519
+ }
520
+ },
521
+ }))
522
+ }
523
+ ctx.effect(() => () => {
524
+ for (const dispose of disposers) dispose()
525
+ }, 'dsh-argo-workspace.tools')
526
+ }
527
+ '@
528
+ $indexPath = Join-Path $dir 'index.js'
529
+ [System.IO.File]::WriteAllText($indexPath, $plugin, (New-Object System.Text.UTF8Encoding $false))
530
+ Write-Host " DSH workspace bridge generated -> $indexPath"
531
+ return $indexPath
532
+ }
533
+
401
534
  function New-DshAgentPresets {
402
535
  # Generate DSH agent presets under ~/.dsh/.agent-presets/<id>/ from
403
536
  # argo/agents/*.agent.md (the same single source the Copilot / Cursor /
@@ -557,20 +690,52 @@ if ($SkipDsh) {
557
690
  Write-Host "[17/19] argo\rules\<WakeupGuideline> -> $DshHome\plugins\dsh-argo-wakeup\index.js (DeepSeek Harness wakeup plugin)"
558
691
  $wakeupDshPath = New-DshWakeupPlugin -DshHome $DshHome -RuleText $ruleSrcContent
559
692
 
560
- Write-Host "[18/19] mcp-argo + argo-wakeup rows -> $patchPath (DeepSeek Harness MCP + wakeup plugin)"
561
- $rows = " - id: mcp-argo`n name: '@deepseek-ai/dsh-mcp-client'`n config:`n serverName: argo`n transport: stdio`n command: node`n args:`n - $argoServer`n"
693
+ Write-Host "[18/19] argo-workspace + argo-wakeup rows -> $patchPath (DeepSeek Harness MCP bridge + wakeup plugin)"
694
+ # The generated dsh-argo-workspace bridge connects directly to the argo
695
+ # server (no dsh-mcp-client row), registers every tool as mcp__argo__* and
696
+ # injects the current session's workspace (SessionHeader.cwd) as the
697
+ # per-call workspaceRoot, so one dsh instance follows the workspace the
698
+ # user switched to. The server honors workspaceRoot only when the workspace
699
+ # is listed in ARGO_WORKSPACE_ROOTS (pass -DshWorkspaces "D:/a;D:/b").
700
+ $bridgeDshPath = New-DshWorkspaceBridge -DshHome $DshHome
701
+ if ($bridgeDshPath) {
702
+ $bridgeUrl = 'file:///' + (($bridgeDshPath -replace '\\', '/').TrimStart('/'))
703
+ $bridgeConfig = " config:`n serverPath: '$argoServer'`n"
704
+ if ($DshWorkspaces) {
705
+ $wsList = @($DshWorkspaces.Replace('\', '/').Split(';') | ForEach-Object { " - $_" }) -join "`n"
706
+ $bridgeConfig += " workspaces:`n" + $wsList + "`n"
707
+ }
708
+ if ($DshCwd) {
709
+ $bridgeConfig += " cwd: $($DshCwd.Replace('\', '/'))`n"
710
+ }
711
+ $rows = " - id: argo-workspace`n name: '$bridgeUrl'`n" + $bridgeConfig
712
+ } else {
713
+ $rows = ''
714
+ }
562
715
  if ($wakeupDshPath) {
563
716
  $pluginUrl = 'file:///' + (($wakeupDshPath -replace '\\', '/').TrimStart('/'))
564
717
  $rows += " - id: argo-wakeup`n name: '$pluginUrl'`n"
565
718
  }
566
- $block = "# BEGIN ArchGraph ARGO deployment (managed by install-argo.ps1)`n- insert:`n" + $rows + "# END ArchGraph ARGO deployment"
567
- Write-DshManagedBlock -Path $patchPath -Block $block -MarkerStart '# BEGIN ArchGraph ARGO deployment' -MarkerEnd '# END ArchGraph ARGO deployment'
719
+ if ($rows) {
720
+ $block = "# BEGIN ArchGraph ARGO deployment (managed by install-argo.ps1)`n- insert:`n" + $rows + "# END ArchGraph ARGO deployment"
721
+ Write-DshManagedBlock -Path $patchPath -Block $block -MarkerStart '# BEGIN ArchGraph ARGO deployment' -MarkerEnd '# END ArchGraph ARGO deployment'
722
+ }
568
723
 
569
724
  Write-Host "[19/19] argo\agents -> $DshHome\.agent-presets\<id> (DeepSeek Harness agent presets)"
570
725
  New-DshAgentPresets -DshHome $DshHome -AgentsSrc (Join-Path $argoDir 'agents')
571
726
 
572
- Write-Host ' Restart `dsh web` to activate the MCP server and the wakeup plugin;'
727
+ Write-Host ' Restart `dsh web` to activate the MCP bridge and the wakeup plugin;'
573
728
  Write-Host ' new sessions pick up the global rule and the argo-init skill automatically.'
729
+ if ($DshWorkspaces) {
730
+ Write-Host " Workspace following enabled: ARGO_WORKSPACE_ROOTS=$DshWorkspaces"
731
+ Write-Host ' The argo tools (mcp__argo__*) now auto-follow the workspace the user switched to'
732
+ Write-Host ' in one dsh instance - no restart needed between workspaces.'
733
+ } else {
734
+ Write-Host ' Multi-workspace: to let one dsh instance follow whichever workspace the user'
735
+ Write-Host ' switches to, re-run with -DshWorkspaces "D:/proj-a;D:/proj-b" (the argo server'
736
+ Write-Host ' only honors workspaces listed in ARGO_WORKSPACE_ROOTS). Without it the server'
737
+ Write-Host ' keeps resolving from the directory dsh was launched from.'
738
+ }
574
739
  }
575
740
 
576
741
  if ($SkipDeps) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archgraph-argo",
3
- "version": "0.9.6",
3
+ "version": "0.9.8",
4
4
  "description": "Deploy the ArchGraph ARGO toolchain, skills, and rules (schema, scripts, argo-init skill, global rule) with one command.",
5
5
  "license": "MIT",
6
6
  "bin": {