dsh-plugin-admin 0.4.3 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -16,6 +16,10 @@
16
16
  * - unarchive(sessionId) → remove from the registry's archived set
17
17
  * - deleteSession(sessionId) → rm the session log directory, detach workspace
18
18
  * accounting, and clear any archived-set entry
19
+ *
20
+ * 3. Namespace `subagentAdmin` (merged from the former dsh-plugin-subagents
21
+ * plugin — see lib/subagent-admin.js): named delegation-tool instances and
22
+ * their external CLI backends, managed as profile cordis.patch.yml rows.
19
23
  */
20
24
 
21
25
  import { spawn, spawnSync } from 'node:child_process'
@@ -26,9 +30,10 @@ import { basename, dirname, join } from 'node:path'
26
30
  import { homedir } from 'node:os'
27
31
  import { fileURLToPath } from 'node:url'
28
32
  import { setTimeout as sleep } from 'node:timers/promises'
33
+ import { applySubagentAdmin } from './subagent-admin.js'
29
34
 
30
35
  /** Services required before this plugin mounts. */
31
- export const inject = ['typert', 'workspaceRegistry', 'sessionPersistence']
36
+ export const inject = ['typert', 'workspaceRegistry', 'sessionPersistence', 'tools', 'subagents']
32
37
 
33
38
  const PLUGIN_SERVICE_KEY = 'pluginAdmin'
34
39
  const PLUGIN_NAMESPACE = 'pluginAdmin'
@@ -187,6 +192,27 @@ function resolveNpmRegistry(profileDir) {
187
192
  return NPM_REGISTRY_DEFAULT
188
193
  }
189
194
 
195
+ /**
196
+ * Resolve a `name@latest` install spec into a pinned `name@<version>` spec by
197
+ * querying the registry for the current `latest` dist-tag. Returns the original
198
+ * `name@latest` unchanged when the registry is unreachable, so an offline
199
+ * upgrade attempt still fails loudly (pnpm will error on the unknown tag)
200
+ * rather than silently no-op'ing. Scoped names (`@scope/name`) are supported.
201
+ * @param profileDir - profile directory (used to locate a local .npmrc).
202
+ * @param name - package name without any version/range suffix.
203
+ * @returns the pinned spec string (e.g. "dsh-plugin-admin@0.5.0").
204
+ */
205
+ async function resolveLatestSpec(profileDir, name) {
206
+ const registry = resolveNpmRegistry(profileDir)
207
+ const latest = await fetchLatestVersion(registry, name)
208
+ if (latest === null) {
209
+ // Registry unreachable: keep the literal @latest so pnpm surfaces the
210
+ // failure (no silent "up to date").
211
+ return name + '@latest'
212
+ }
213
+ return name + '@' + latest
214
+ }
215
+
190
216
  /**
191
217
  * Query the npm registry for the `latest` dist-tag version of one package.
192
218
  * Strict timeout; returns null on any failure so a dead registry never
@@ -1185,7 +1211,19 @@ export function apply(ctx) {
1185
1211
  if (typeof spec !== 'string' || spec.trim() === '') {
1186
1212
  throw new Error('plugin-admin: install requires a spec string')
1187
1213
  }
1188
- const operand = assertPnpmOperand('install spec', spec)
1214
+ const rawOperand = assertPnpmOperand('install spec', spec)
1215
+ // A bare `@latest` dist-tag is ambiguous for pnpm when the manifest
1216
+ // already pins a satisified range (e.g. "^0.4.2"): pnpm resolves
1217
+ // @latest against the range, concludes "Already up to date", and exits
1218
+ // 0 WITHOUT fetching the newer published version — the upgrade silently
1219
+ // no-ops. Resolve the real latest version from the registry first and
1220
+ // pin it exactly so pnpm is forced to bump the constraint and download.
1221
+ // The client already sends an exact `name@<version>` when it knows the
1222
+ // latest (from checkUpdates); this is the server-side safety net for
1223
+ // any caller that still passes `@latest`.
1224
+ const operand = rawOperand.endsWith('@latest')
1225
+ ? await resolveLatestSpec(profileDir, rawOperand.slice(0, -'@latest'.length))
1226
+ : rawOperand
1189
1227
  return enqueue(async () => {
1190
1228
  const output = await runPnpm(profileDir, ['add', operand])
1191
1229
  reconcileBundles(profileDir)
@@ -2010,6 +2048,14 @@ export function apply(ctx) {
2010
2048
  Object.defineProperty(mcpService, 'typertRemote', { value: mcpBinding, enumerable: false })
2011
2049
  ctx.provide(MCP_SERVICE_KEY, mcpService)
2012
2050
 
2051
+ /* ---------------------- Subagent Admin (merged) -------------------------- */
2052
+ // Mount the subagentAdmin remote (formerly the standalone dsh-plugin-subagents
2053
+ // plugin). It shares this apply's serial queue: mcpAdmin and subagentAdmin
2054
+ // both read-modify-write the profile's cordis.patch.yml. The typert registry
2055
+ // allows ONE registration per package name, so the subagent invocations ride
2056
+ // the unified descriptor below instead of registering their own.
2057
+ const subagentInvocations = applySubagentAdmin(ctx, enqueue)
2058
+
2013
2059
  /* ---------------------- Typert Descriptors Register ---------------------- */
2014
2060
  const specParam = [{ name: 'spec', wire: 'spec', source: 'json', codec: { mode: 'src-json' } }]
2015
2061
  const nameParam = [{ name: 'name', wire: 'name', source: 'json', codec: { mode: 'src-json' } }]
@@ -2152,6 +2198,8 @@ export function apply(ctx) {
2152
2198
  parameters: [{ name: 'id', wire: 'id', source: 'json', codec: { mode: 'src-json' } }],
2153
2199
  result: { mode: 'src-json' },
2154
2200
  },
2201
+ // subagentAdmin (merged from dsh-plugin-subagents)
2202
+ ...subagentInvocations,
2155
2203
  ],
2156
- }), 'plugin-admin: typert descriptors (plugins, sessions, fs, mcp)')
2204
+ }), 'plugin-admin: typert descriptors (plugins, sessions, fs, mcp, subagents)')
2157
2205
  }