external-subagent 0.5.4 → 0.5.5

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.
@@ -69,9 +69,36 @@ export async function updateCommand(paths, args = [], daemon = {}) {
69
69
  // returns with the daemon still serving spawns. A pure reaffirmal that
70
70
  // activates nothing drains only for its claim and needs no candidate.
71
71
  const preflight = daemon.preflightUpdate || preflightUpdate;
72
- if (!(reconciling && !coordinate)) preflight({ version: requestedVersion });
72
+ let candidate = null;
73
+ if (!(reconciling && !coordinate)) {
74
+ candidate = preflight({ version: requestedVersion });
75
+ }
76
+ // A JavaScript-only release ships a native payload byte-identical to the
77
+ // one the running daemon already executes. Draining and restarting the
78
+ // service for it would bounce every live subagent task for nothing, and
79
+ // the health gate would then refuse the "new" payload anyway: the daemon
80
+ // self-reports the version baked into the (unchanged) binary, which can
81
+ // never equal the requested JS-only version. So when the running daemon's
82
+ // self-reported artifact digest matches the candidate's verified daemon
83
+ // digest, this run skips the drain/service-restart entirely: the JS state
84
+ // still activates, host bindings still reconcile, and the service keeps
85
+ // executing the very bytes it already runs. The restart path stays
86
+ // untouched for any real binary change (digest mismatch) and for offline
87
+ // daemons (nothing to compare against; the activation boots the service).
88
+ let jsOnlyRestartSkip = false;
89
+ if (candidate?.daemon?.digest) {
90
+ try {
91
+ const live = await rpc(socket, 'status', {});
92
+ const liveArtifact = live?.identity?.daemon?.artifact;
93
+ if (liveArtifact?.sha256 === candidate.daemon.digest) jsOnlyRestartSkip = true;
94
+ } catch { /* offline or unanswerable daemon: take the normal restart path */ }
95
+ }
73
96
  let status = null;
74
97
  let online = true;
98
+ if (jsOnlyRestartSkip) {
99
+ online = false;
100
+ status = { ready_for_activation: true, activation_claim: null, js_only_restart_skipped: true };
101
+ } else {
75
102
  try {
76
103
  status = await rpc(socket, 'drain', cancelActive ? { cancel_active: true } : {});
77
104
  } catch (error) {
@@ -99,6 +126,7 @@ export async function updateCommand(paths, args = [], daemon = {}) {
99
126
  }
100
127
  status = await rpc(socket, 'activate-ready', {});
101
128
  }
129
+ }
102
130
  const claim = online ? status.activation_claim : `offline-${process.pid}-${Date.now()}-activation`;
103
131
  if (!claim) return { ...status, activation_claim: null, update: 'not_activated' };
104
132
  if (prior?.status === 'success' && prior.claim === claim
@@ -123,7 +151,11 @@ export async function updateCommand(paths, args = [], daemon = {}) {
123
151
  let partialHosts = null;
124
152
  try {
125
153
  const serviceInstalled = typeof daemon.hasInstalledService === 'function' ? daemon.hasInstalledService(paths) : hasInstalledService(paths);
126
- const serviceDue = serviceInstalled && !daemon.skipServiceActivation;
154
+ // A JS-only release skips the service restart (see jsOnlyRestartSkip):
155
+ // the running daemon already executes the candidate's exact daemon bytes,
156
+ // so a bootout/bootstrap would only bounce live tasks and then fail the
157
+ // version-bearing health gate against the unchanged binary.
158
+ const serviceDue = serviceInstalled && !daemon.skipServiceActivation && !jsOnlyRestartSkip;
127
159
  result = (reconciling && !coordinate)
128
160
  ? { ...reconcileInstallation(paths, { cancelActive, yes }), homes: reconcileCodexHomes(paths), zcode: reconcileZcodeBinding(paths), dsh: reconcileDshBindings(paths) }
129
161
  : await (daemon.updateInstallation || updateInstallation)(paths, { version: requestedVersion, deferCodexSync: serviceDue });
@@ -163,8 +195,8 @@ export async function updateCommand(paths, args = [], daemon = {}) {
163
195
  partialHosts = partialHostSync(result);
164
196
  atomicWrite(receiptPath(paths), jsonBytes(partialHosts
165
197
  ? { claim, version: receiptVersion, status: 'partial', retryable: true, homes: result.homes, zcode: result.zcode, result }
166
- : { claim, version: receiptVersion, status: 'success', result }));
167
- if (partialHosts === null) return result;
198
+ : { claim, version: receiptVersion, status: 'success', ...(jsOnlyRestartSkip ? { js_only_restart_skipped: true } : {}), result }));
199
+ if (partialHosts === null) return jsOnlyRestartSkip ? { ...result, js_only_restart_skipped: true } : result;
168
200
  } catch (error) {
169
201
  // Snapshot what the updater recorded before rollback replaces it, so the
170
202
  // receipt keeps the candidate and active evidence the retry will need.
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.5.4';
15
+ export const VERSION = '0.5.5';
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';
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema_version": 1,
3
3
  "product": "external-subagent",
4
- "version": "0.5.4",
4
+ "version": "0.5.5",
5
5
  "platform": "darwin-arm64",
6
6
  "files": [
7
7
  {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema_version": 1,
3
3
  "product": "external-subagent",
4
- "version": "0.5.4",
4
+ "version": "0.5.5",
5
5
  "platform": "linux-x64",
6
6
  "files": [
7
7
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "external-subagent",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
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",