external-subagent 0.6.1 → 0.6.2

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,8 +1,9 @@
1
1
  import { CliError } from '../errors.mjs';
2
2
  import { installMcp, installPlugin, uninstallPlugin } from '../install/codex.mjs';
3
3
  import { installZcodePlugin, uninstallZcodePlugin } from '../install/zcode.mjs';
4
- import { installDshPlugin, uninstallDshPlugin } from '../install/dsh.mjs';
4
+ import { installDshPlugin, uninstallDshPlugin, dshHomeFor } from '../install/dsh.mjs';
5
5
  import { registerCodexHome, unregisterCodexHome } from '../install/reconcile.mjs';
6
+ import { registerDshBinding, unregisterDshBinding } from '../install/dsh-registry.mjs';
6
7
  import { verifyPayload } from '../install/payload.mjs';
7
8
 
8
9
  // Command surface for the managed host bindings. `install-plugin` takes an
@@ -68,7 +69,23 @@ export function pluginCommand(paths, args) {
68
69
  // error at the CLI parsing boundary with the other flag-combination
69
70
  // errors instead of deep inside the installer.
70
71
  if (!options.profile) throw new CliError('INVALID_ARGUMENT', 'the dsh host requires --profile <name>', 2);
71
- return options.uninstall ? uninstallDshPlugin(paths, installerOptions) : installDshPlugin(paths, installerOptions);
72
+ const result = options.uninstall ? uninstallDshPlugin(paths, installerOptions) : installDshPlugin(paths, installerOptions);
73
+ // Registry bookkeeping survives a dsh-desktop rewrite that drops the
74
+ // managed block: a registered profile is RESTORED by reconcile, an
75
+ // unregistered one is forgotten. Dry-runs never touch the registry.
76
+ if (!options.dry_run) {
77
+ const dshHome = dshHomeFor(installerOptions, paths);
78
+ if (options.uninstall) {
79
+ if (result.uninstalled) {
80
+ const claim = unregisterDshBinding(paths, dshHome, options.profile);
81
+ result.claim_released = claim.unregistered;
82
+ }
83
+ } else if (result.installed) {
84
+ const claim = registerDshBinding(paths, dshHome, options.profile, { status: 'claimed' });
85
+ result.claim = { registered: claim.registered, deduplicated: claim.deduplicated, bindings: claim.bindings };
86
+ }
87
+ }
88
+ return result;
72
89
  }
73
90
  if (options.profile) throw new CliError('INVALID_ARGUMENT', '--profile applies to the dsh host only', 2);
74
91
  if (options.dshHome) throw new CliError('INVALID_ARGUMENT', '--dsh-home applies to the dsh host only', 2);
package/cli/constants.mjs CHANGED
@@ -12,7 +12,7 @@ const IS_DEBUG_VARIANT = process.env.EXTERNAL_SUBAGENT_VARIANT === 'debug';
12
12
 
13
13
  export const PRODUCT_NAME = IS_DEBUG_VARIANT ? 'external-subagent-debug' : 'external-subagent';
14
14
  export const PRODUCT_ID = IS_DEBUG_VARIANT ? 'external_subagent_debug' : 'external_subagent';
15
- export const VERSION = '0.6.1';
15
+ export const VERSION = '0.6.2';
16
16
  export const LAUNCH_AGENT_LABEL = IS_DEBUG_VARIANT ? 'com.external-subagent-debug.daemon' : 'com.external-subagent.daemon';
17
17
  export const DAEMON_BIN_NAME = IS_DEBUG_VARIANT ? 'external-subagent-debugd' : 'external-subagentd';
18
18
  export const MCP_BIN_NAME = IS_DEBUG_VARIANT ? 'external-subagent-debug-mcp' : 'external-subagent-mcp';
@@ -0,0 +1,143 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { CliError } from '../errors.mjs';
4
+ import { atomicWrite, jsonBytes, readOptional } from '../fs-atomic.mjs';
5
+
6
+ // D28: the persistent registry of bound DSH profiles. The dsh binding is
7
+ // derivable from the patch sentinel in daily operation, but the sentinel
8
+ // itself lives inside a file the dsh desktop rewrites wholesale on upgrade
9
+ // (OBSERVED 2026-10-28, dsh 0.2.1-rc.3: both bound profiles lost the
10
+ // managed block AND the staged skill, while the product's sentinel-only
11
+ // reconcile reported bound:false and silently forgot the machines ever
12
+ // bound). This registry remembers what the user explicitly bound so
13
+ // reconcile can RESTORE a dropped binding instead of merely refreshing
14
+ // surviving ones. It follows the codex-homes D08 model: atomic writes,
15
+ // per-entry status bookkeeping, and corruption recovery that preserves the
16
+ // bad bytes as evidence and continues with an empty table rather than
17
+ // aborting the caller.
18
+
19
+ const REGISTRY_SCHEMA_VERSION = 1;
20
+
21
+ export function dshBindingsRegistryPath(paths) {
22
+ return path.join(paths.data, 'dsh-bindings.json');
23
+ }
24
+
25
+ function emptyRegistry() {
26
+ return { schema_version: REGISTRY_SCHEMA_VERSION, product: 'external-subagent', bindings: [] };
27
+ }
28
+
29
+ // Resolve a home to its canonical registry key. A plain realpath fails on
30
+ // a not-yet-created directory (a dsh home registered before its first
31
+ // boot), and on macOS the /var -> /private/var symlink then yields the
32
+ // UNRESOLVED prefix — so the same home would register and reconcile under
33
+ // two different strings depending on whether it existed at the time.
34
+ // Resolve the deepest EXISTING ancestor and append the remainder instead:
35
+ // the key is stable across creation, removal, and symlinked tmpdirs.
36
+ export function canonicalHome(home) {
37
+ if (typeof home !== 'string' || home.length === 0) throw new CliError('INVALID_ARGUMENT', 'dsh home must be a non-empty string', 2);
38
+ const absolute = path.resolve(home);
39
+ try { return fs.realpathSync(absolute); } catch { /* fall through to ancestor resolution */ }
40
+ const parent = path.dirname(absolute);
41
+ if (parent === absolute) return absolute;
42
+ return path.join(canonicalHome(parent), path.basename(absolute));
43
+ }
44
+
45
+ function resolveHome(home) {
46
+ return canonicalHome(home);
47
+ }
48
+
49
+ function normalizeEntry(entry) {
50
+ if (!entry || typeof entry.home !== 'string' || typeof entry.profile !== 'string') {
51
+ throw new CliError('DSH_BINDINGS_REGISTRY_INVALID', 'registry entry is missing its home or profile path');
52
+ }
53
+ if (/[\\/]/u.test(entry.profile)) {
54
+ throw new CliError('DSH_BINDINGS_REGISTRY_INVALID', 'registry entry profile must not contain path separators');
55
+ }
56
+ return {
57
+ home: entry.home,
58
+ profile: entry.profile,
59
+ claimed_at_ms: Number.isInteger(entry.claimed_at_ms) ? entry.claimed_at_ms : null,
60
+ last_sync_ms: Number.isInteger(entry.last_sync_ms) ? entry.last_sync_ms : null,
61
+ last_status: typeof entry.last_status === 'string' ? entry.last_status : null,
62
+ };
63
+ }
64
+
65
+ function parseRegistry(bytes, file) {
66
+ const doc = JSON.parse(bytes.toString('utf8'));
67
+ if (doc?.schema_version !== REGISTRY_SCHEMA_VERSION || doc?.product !== 'external-subagent' || !Array.isArray(doc.bindings)) {
68
+ throw new CliError('DSH_BINDINGS_REGISTRY_INVALID', 'registry identity or bindings table is invalid');
69
+ }
70
+ const registry = emptyRegistry();
71
+ registry.bindings = doc.bindings.map(normalizeEntry);
72
+ return registry;
73
+ }
74
+
75
+ // Corrupted registry: preserve the bytes for inspection, atomically stage a
76
+ // fresh registry, and report a recoverable error — never abort the caller.
77
+ export function loadDshBindings(paths) {
78
+ const file = dshBindingsRegistryPath(paths);
79
+ const bytes = readOptional(file);
80
+ if (bytes === null) return { registry: emptyRegistry(), recovery: null };
81
+ try {
82
+ return { registry: parseRegistry(bytes, file), recovery: null };
83
+ } catch (error) {
84
+ const backup = `${file}.corrupt-${Date.now()}`;
85
+ try { fs.renameSync(file, backup); } catch (renameError) {
86
+ throw new CliError('DSH_BINDINGS_REGISTRY_CORRUPT', `registry is corrupted and could not be preserved: ${renameError.message}`);
87
+ }
88
+ atomicWrite(file, jsonBytes(emptyRegistry()));
89
+ return {
90
+ registry: emptyRegistry(),
91
+ recovery: { status: 'recovered', code: 'DSH_BINDINGS_REGISTRY_CORRUPT', backup, error: error.message },
92
+ };
93
+ }
94
+ }
95
+
96
+ export function persistRegistry(paths, registry) {
97
+ atomicWrite(dshBindingsRegistryPath(paths), jsonBytes(registry));
98
+ }
99
+
100
+ export function registerDshBinding(paths, dshHome, profile, meta = {}) {
101
+ const { registry, recovery } = loadDshBindings(paths);
102
+ const home = resolveHome(dshHome);
103
+ const existing = registry.bindings.find((entry) => entry.home === home && entry.profile === profile);
104
+ let entry;
105
+ if (existing) {
106
+ entry = existing;
107
+ if (typeof meta.status === 'string') entry.last_status = meta.status;
108
+ } else {
109
+ entry = {
110
+ home,
111
+ profile,
112
+ claimed_at_ms: Date.now(),
113
+ last_sync_ms: null,
114
+ last_status: typeof meta.status === 'string' ? meta.status : 'claimed',
115
+ };
116
+ registry.bindings.push(entry);
117
+ }
118
+ persistRegistry(paths, registry);
119
+ return { registered: true, home, profile, deduplicated: Boolean(existing), bindings: registry.bindings.length, recovery };
120
+ }
121
+
122
+ export function unregisterDshBinding(paths, dshHome, profile) {
123
+ const { registry } = loadDshBindings(paths);
124
+ const home = resolveHome(dshHome);
125
+ const remaining = registry.bindings.filter((entry) => !(entry.home === home && entry.profile === profile));
126
+ const unregistered = remaining.length !== registry.bindings.length;
127
+ registry.bindings = remaining;
128
+ if (unregistered) persistRegistry(paths, registry);
129
+ return { unregistered, home, profile, bindings: registry.bindings.length };
130
+ }
131
+
132
+ // Product uninstall: no profile remains claimed. The registry file itself
133
+ // stays inside retained product data, matching the codex-homes posture.
134
+ export function unregisterAllDshBindings(paths) {
135
+ const { registry } = loadDshBindings(paths);
136
+ const released = registry.bindings.map((entry) => ({ home: entry.home, profile: entry.profile }));
137
+ const file = dshBindingsRegistryPath(paths);
138
+ if (released.length > 0 || fs.existsSync(file)) {
139
+ registry.bindings = [];
140
+ persistRegistry(paths, registry);
141
+ }
142
+ return { unregistered: released.length, bindings: released };
143
+ }
@@ -7,6 +7,7 @@ import { atomicWrite, readOptional, restoreOptional } from '../fs-atomic.mjs';
7
7
  import { MCP_BIN_NAME } from '../constants.mjs';
8
8
  import { nativeBinary, pluginSourceRoot, PLUGIN_NAME } from './layout.mjs';
9
9
  import { guardedRestores } from './plugin-stage.mjs';
10
+ import { loadDshBindings, persistRegistry as persistDshRegistry, dshBindingsRegistryPath, canonicalHome } from './dsh-registry.mjs';
10
11
 
11
12
  // Managed DSH host binding. DSH boots profiles as an ordered stack of
12
13
  // plugin-bundle patch layers under the user's own overrides; the user layer
@@ -17,9 +18,11 @@ import { guardedRestores } from './plugin-stage.mjs';
17
18
  // into the user skill root (<dshHome>/skills), which dsh-skill-filesystem
18
19
  // scans by default (rank 400; OBSERVED against dsh 0.2.0-rc.2). It never
19
20
  // touches the profile's package.json, bundles, node_modules, or any dsh
20
- // market/plugin-install state. There is no registry file: the binding is
21
- // fully derivable from the patch sentinel plus the staged skill, so
22
- // reconcile and uninstall detect rather than remember it (the zcode model).
21
+ // market/plugin-install state. Claims also live in the persistent
22
+ // dsh-bindings.json registry (dsh-registry.mjs): the dsh desktop rewrites
23
+ // cordis.patch.yml wholesale on upgrade and DROPS the managed block
24
+ // (OBSERVED 2026-10-28), so reconcile pairs the sentinel scan with the
25
+ // registry to RESTORE a dropped binding instead of forgetting it.
23
26
 
24
27
  // The MCP client plugin is resolved by the dsh loader from the CLI's own
25
28
  // install tree when the bare package name is used (OBSERVED: a fresh
@@ -546,61 +549,135 @@ export function uninstallDshPlugin(paths, options = {}) {
546
549
  };
547
550
  }
548
551
 
549
- // Stateless reconcile for update flows (the zcode model): every profile
550
- // directory whose patch file carries the sentinel is rebound; the skill is
551
- // refreshed alongside. Unknown/corrupt states are reported, never fatal to
552
- // the caller's broader reconcile.
552
+ // Reconcile for update flows: the persistent registry (dsh-bindings.json)
553
+ // UNION the sentinel scan. The scan alone follows the zcode model — detect
554
+ // rather than remember — but the dsh desktop rewrites cordis.patch.yml
555
+ // wholesale on upgrade and DROPS the managed block (OBSERVED 2026-10-28),
556
+ // after which a sentinel-only reconcile silently forgets the profile was
557
+ // ever bound. Registry entries therefore drive a RESTORE: a registered
558
+ // profile whose patch lost the sentinel is re-bound (status `rebound`),
559
+ // and one whose profile directory itself vanished is reported
560
+ // (`missing_profile_dir`) without failing the reconcile. Profiles bearing
561
+ // the sentinel but absent from the registry (bound by an older build, or
562
+ // by hand) are adopted into the registry so future drops restore too.
553
563
  export function reconcileDshBindings(paths, options = {}) {
554
564
  const dshHome = dshHomeFor(options, paths);
565
+ // Registry entries store realpath-resolved homes (registerDshBinding
566
+ // resolves); compare and dedupe against the SAME resolved form, or a
567
+ // symlinked dshHome (/var -> /private/var on macOS tmpdirs) would never
568
+ // match its own registry claims.
569
+ const resolvedHome = canonicalHome(dshHome);
555
570
  const profilesRoot = path.join(dshHome, 'profiles');
556
- let entries;
571
+ const { registry, recovery } = loadDshBindings(paths);
572
+ const results = [];
573
+ const visited = new Set();
574
+ let directories = [];
575
+ let profilesRootAbsent = false;
557
576
  try {
558
- entries = fs.readdirSync(profilesRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
577
+ directories = fs.readdirSync(profilesRoot, { withFileTypes: true })
578
+ .filter((entry) => entry.isDirectory())
579
+ .map((entry) => entry.name);
559
580
  } catch (error) {
560
- if (error?.code === 'ENOENT') return { bound: false, profiles: [], note: 'no dsh profiles directory' };
561
- return { bound: false, profiles: [], error: { code: 'DSH_PROFILES_UNREADABLE', message: error.message } };
581
+ if (error?.code === 'ENOENT') profilesRootAbsent = true;
582
+ else {
583
+ return { bound: false, profiles: [], error: { code: 'DSH_PROFILES_UNREADABLE', message: error.message }, ...(recovery ? { recovery } : {}) };
584
+ }
562
585
  }
563
- const results = [];
564
- for (const entry of entries) {
565
- if (DSH_FORBIDDEN_PROFILES.includes(entry.name)) continue;
566
- const patchFile = path.join(profilesRoot, entry.name, 'cordis.patch.yml');
586
+ const directorySet = new Set(directories);
587
+ // Sentinel scan: refresh every surviving binding and adopt unregistered ones.
588
+ for (const name of directories) {
589
+ if (DSH_FORBIDDEN_PROFILES.includes(name)) continue;
590
+ const patchFile = path.join(profilesRoot, name, 'cordis.patch.yml');
567
591
  const bytes = readOptional(patchFile);
568
592
  if (bytes === null || !bytes.toString('utf8').includes(SENTINEL)) continue;
593
+ visited.add(`${resolvedHome}\u0000${name}`);
594
+ try {
595
+ installDshPlugin(paths, { ...options, profile: name });
596
+ upsertRegistryEntry(registry, resolvedHome, name, 'updated');
597
+ results.push({ profile: name, status: 'updated', idempotent: true });
598
+ } catch (error) {
599
+ results.push({ profile: name, status: 'failed', error: { code: error.code || 'DSH_BINDING_FAILED', message: error.message } });
600
+ }
601
+ }
602
+ // Registry pass: restore registered profiles whose sentinel was dropped.
603
+ for (const entry of registry.bindings) {
604
+ if (entry.home !== resolvedHome) continue;
605
+ if (visited.has(`${entry.home}\u0000${entry.profile}`)) continue;
606
+ if (DSH_FORBIDDEN_PROFILES.includes(entry.profile)) continue;
607
+ if (!directorySet.has(entry.profile)) {
608
+ entry.last_status = 'missing_profile_dir';
609
+ results.push({ profile: entry.profile, status: 'missing_profile_dir' });
610
+ continue;
611
+ }
569
612
  try {
570
- const install = installDshPlugin(paths, { ...options, profile: entry.name });
571
- results.push({ profile: entry.name, status: 'updated', idempotent: Boolean(install.idempotent) });
613
+ installDshPlugin(paths, { ...options, dshHome: entry.home, profile: entry.profile });
614
+ entry.last_status = 'rebound';
615
+ entry.last_sync_ms = Date.now();
616
+ results.push({ profile: entry.profile, status: 'rebound' });
572
617
  } catch (error) {
573
- results.push({ profile: entry.name, status: 'failed', error: { code: error.code || 'DSH_BINDING_FAILED', message: error.message } });
618
+ entry.last_status = 'failed';
619
+ results.push({ profile: entry.profile, status: 'failed', error: { code: error.code || 'DSH_BINDING_FAILED', message: error.message } });
574
620
  }
575
621
  }
576
- return { bound: results.length > 0, profiles: results };
622
+ if (results.length > 0 || fs.existsSync(dshBindingsRegistryPath(paths))) {
623
+ persistDshRegistry(paths, registry);
624
+ }
625
+ return {
626
+ bound: results.length > 0,
627
+ profiles: results,
628
+ ...(profilesRootAbsent ? { note: 'no dsh profiles directory' } : {}),
629
+ ...(recovery ? { recovery } : {}),
630
+ };
631
+ }
632
+
633
+ // Insert or refresh a registry entry in place (the loaded registry object is
634
+ // the caller's working copy; persisting happens once at the end).
635
+ function upsertRegistryEntry(registry, dshHome, profile, status) {
636
+ const home = canonicalHome(dshHome);
637
+ const existing = registry.bindings.find((entry) => entry.home === home && entry.profile === profile);
638
+ if (existing) {
639
+ existing.last_status = status;
640
+ existing.last_sync_ms = Date.now();
641
+ } else {
642
+ registry.bindings.push({
643
+ home,
644
+ profile,
645
+ claimed_at_ms: Date.now(),
646
+ last_sync_ms: Date.now(),
647
+ last_status: status,
648
+ });
649
+ }
577
650
  }
578
651
 
579
652
  // Product uninstall: release every detected binding at once. Detection is
580
- // the same sentinel scan as reconcile; each profile is uninstalled
581
- // independently (a failure on one never skips the rest, matching the
582
- // per-home codex release posture), and the shared staged skill is removed
583
- // when no bound profile remains.
653
+ // the same sentinel scan plus the registry, so a desktop-rewritten patch
654
+ // (sentinel dropped) is still unregistered even though its block is gone.
655
+ // Each profile is uninstalled independently (a failure on one never skips
656
+ // the rest, matching the per-home codex release posture), and the shared
657
+ // staged skill is removed when no bound profile remains.
584
658
  export function uninstallAllDshBindings(paths, options = {}) {
585
659
  const dshHome = dshHomeFor(options, paths);
586
660
  const profilesRoot = path.join(dshHome, 'profiles');
587
- let entries;
661
+ let entries = [];
588
662
  try {
589
- entries = fs.readdirSync(profilesRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
590
- } catch {
591
- return { released: 0, profiles: [] };
663
+ entries = fs.readdirSync(profilesRoot, { withFileTypes: true })
664
+ .filter((entry) => entry.isDirectory())
665
+ .map((entry) => entry.name);
666
+ } catch { /* registry pass below still releases claims */ }
667
+ const names = new Set(entries);
668
+ const resolvedHome = canonicalHome(dshHome);
669
+ const { registry } = loadDshBindings(paths);
670
+ for (const entry of registry.bindings) {
671
+ if (entry.home === resolvedHome) names.add(entry.profile);
592
672
  }
593
673
  const results = [];
594
- for (const entry of entries) {
595
- if (DSH_FORBIDDEN_PROFILES.includes(entry.name)) continue;
596
- const patchFile = path.join(profilesRoot, entry.name, 'cordis.patch.yml');
597
- const bytes = readOptional(patchFile);
598
- if (bytes === null || !bytes.toString('utf8').includes(SENTINEL)) continue;
674
+ for (const name of names) {
675
+ if (DSH_FORBIDDEN_PROFILES.includes(name)) continue;
599
676
  try {
600
- const removed = uninstallDshPlugin(paths, { ...options, profile: entry.name });
601
- results.push({ profile: entry.name, status: removed.uninstalled ? 'released' : 'absent' });
677
+ const removed = uninstallDshPlugin(paths, { ...options, profile: name });
678
+ results.push({ profile: name, status: removed.uninstalled ? 'released' : 'absent' });
602
679
  } catch (error) {
603
- results.push({ profile: entry.name, status: 'failed', error: { code: error.code || 'DSH_BINDING_RELEASE_FAILED', message: error.message } });
680
+ results.push({ profile: name, status: 'failed', error: { code: error.code || 'DSH_BINDING_RELEASE_FAILED', message: error.message } });
604
681
  }
605
682
  }
606
683
  // The skill is shared across profiles; remove it once no binding remains.
@@ -609,5 +686,10 @@ export function uninstallAllDshBindings(paths, options = {}) {
609
686
  unstageSkill(dshHome, options.source || pluginSourceRoot());
610
687
  } catch { /* reported per-profile above; a stuck skill never fails the release */ }
611
688
  }
612
- return { released: results.filter((result) => result.status === 'released').length, profiles: results };
689
+ // Release every registry claim for THIS dsh home (other homes keep theirs).
690
+ const before = registry.bindings.length;
691
+ registry.bindings = registry.bindings.filter((entry) => entry.home !== resolvedHome);
692
+ const registry_cleared = before !== registry.bindings.length || fs.existsSync(dshBindingsRegistryPath(paths));
693
+ if (registry_cleared) persistDshRegistry(paths, registry);
694
+ return { released: results.filter((result) => result.status === 'released').length, profiles: results, registry_cleared };
613
695
  }
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "schema_version": 1,
3
3
  "product": "external-subagent",
4
- "version": "0.6.1",
4
+ "version": "0.6.2",
5
5
  "platform": "darwin-arm64",
6
6
  "files": [
7
7
  {
8
8
  "name": "external-subagentd",
9
- "bytes": 10436368,
9
+ "bytes": 10436400,
10
10
  "mode": "755",
11
- "sha256": "4aebf0b109a33e9b2b1a7897287d1249f7e3792ae7848560f0d08d9365495d06"
11
+ "sha256": "1a954915bde8dc5bafd1d73f9a767a55068a54c7707cafe1508a8ddebd35128d"
12
12
  },
13
13
  {
14
14
  "name": "external-subagent-mcp",
15
15
  "bytes": 935776,
16
16
  "mode": "755",
17
- "sha256": "1960fd2fb95e5e7125eec8ea21456cddc60e266b65ae67f247f611bd855a233c"
17
+ "sha256": "e0f51f5b901bdb5c01bff16fa8dc136df9583b73aea07c687c24f6949a8dd066"
18
18
  }
19
19
  ]
20
20
  }
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "schema_version": 1,
3
3
  "product": "external-subagent",
4
- "version": "0.6.1",
4
+ "version": "0.6.2",
5
5
  "platform": "linux-x64",
6
6
  "files": [
7
7
  {
8
8
  "name": "external-subagentd",
9
- "bytes": 12031656,
9
+ "bytes": 12031728,
10
10
  "mode": "755",
11
- "sha256": "3e114f16cc88e5ce287013694f5c239616195769178af3b8f0c81d7ee8373c95"
11
+ "sha256": "ef39528c0c033016f06a6af0bbf727d15ecbaca3f697ee3d5ca321abb4ca75cd"
12
12
  },
13
13
  {
14
14
  "name": "external-subagent-mcp",
15
- "bytes": 1025536,
15
+ "bytes": 1025480,
16
16
  "mode": "755",
17
- "sha256": "d859ad4eed3e7e44faf377982fe0d641fb78157bd4da7a05256eeadc8eb545cb"
17
+ "sha256": "fac84526602dca1cd82574f82afe28e5b6700532297670e33b8fb092c1fad7cb"
18
18
  }
19
19
  ]
20
20
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "external-subagent",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "Manage bounded ZCode, DeepSeek Harness (DSH), Codex, and Google Antigravity (agy) subagent tasks through a local MCP service for any local host (Codex, ZCode, or a custom MCP client)",
5
5
  "type": "module",
6
6
  "license": "MIT",