@beeos-ai/device-agent 0.15.0 → 0.16.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.
Files changed (240) hide show
  1. package/README.md +35 -0
  2. package/dist/adapters/agent-gateway-client.d.ts +6 -68
  3. package/dist/adapters/agent-gateway-client.js +7 -67
  4. package/dist/adapters/agent-gateway-client.js.map +1 -1
  5. package/dist/adapters/device-compat.d.ts +1 -2
  6. package/dist/adapters/device-compat.js +2 -52
  7. package/dist/adapters/device-compat.js.map +1 -1
  8. package/dist/agent-runtime/action-guard.js +92 -17
  9. package/dist/agent-runtime/action-guard.js.map +1 -1
  10. package/dist/agent-runtime/adapters/base.d.ts +13 -9
  11. package/dist/agent-runtime/adapters/base.js.map +1 -1
  12. package/dist/agent-runtime/adapters/general-flash.d.ts +3 -3
  13. package/dist/agent-runtime/adapters/general-flash.js +2 -2
  14. package/dist/agent-runtime/adapters/general-flash.js.map +1 -1
  15. package/dist/agent-runtime/adapters/native-tools.d.ts +56 -0
  16. package/dist/agent-runtime/adapters/native-tools.js +416 -0
  17. package/dist/agent-runtime/adapters/native-tools.js.map +1 -0
  18. package/dist/agent-runtime/adapters/openai.d.ts +1 -2
  19. package/dist/agent-runtime/adapters/openai.js +5 -5
  20. package/dist/agent-runtime/adapters/openai.js.map +1 -1
  21. package/dist/agent-runtime/adapters/pro.d.ts +7 -37
  22. package/dist/agent-runtime/adapters/pro.js +7 -249
  23. package/dist/agent-runtime/adapters/pro.js.map +1 -1
  24. package/dist/agent-runtime/advanced/app-card.js +9 -2
  25. package/dist/agent-runtime/advanced/app-card.js.map +1 -1
  26. package/dist/agent-runtime/advanced/subgoal-verifier.d.ts +1 -0
  27. package/dist/agent-runtime/advanced/subgoal-verifier.js +30 -36
  28. package/dist/agent-runtime/advanced/subgoal-verifier.js.map +1 -1
  29. package/dist/agent-runtime/advanced/task-state.js +4 -9
  30. package/dist/agent-runtime/advanced/task-state.js.map +1 -1
  31. package/dist/agent-runtime/agent-runner.d.ts +2 -11
  32. package/dist/agent-runtime/agent-runner.js +69 -34
  33. package/dist/agent-runtime/agent-runner.js.map +1 -1
  34. package/dist/agent-runtime/core.d.ts +24 -46
  35. package/dist/agent-runtime/core.js +161 -228
  36. package/dist/agent-runtime/core.js.map +1 -1
  37. package/dist/agent-runtime/execution-checks.d.ts +40 -0
  38. package/dist/agent-runtime/execution-checks.js +133 -0
  39. package/dist/agent-runtime/execution-checks.js.map +1 -0
  40. package/dist/agent-runtime/execution-mode.d.ts +1 -8
  41. package/dist/agent-runtime/execution-mode.js +10 -30
  42. package/dist/agent-runtime/execution-mode.js.map +1 -1
  43. package/dist/agent-runtime/feedback.d.ts +4 -28
  44. package/dist/agent-runtime/feedback.js +4 -26
  45. package/dist/agent-runtime/feedback.js.map +1 -1
  46. package/dist/agent-runtime/form-prerequisites.d.ts +3 -0
  47. package/dist/agent-runtime/form-prerequisites.js +52 -0
  48. package/dist/agent-runtime/form-prerequisites.js.map +1 -0
  49. package/dist/agent-runtime/index.d.ts +2 -8
  50. package/dist/agent-runtime/index.js +2 -8
  51. package/dist/agent-runtime/index.js.map +1 -1
  52. package/dist/agent-runtime/{modes/orchestrator → knowledge}/app-guide.d.ts +1 -1
  53. package/dist/agent-runtime/{modes/orchestrator → knowledge}/app-guide.js +1 -1
  54. package/dist/agent-runtime/knowledge/app-guide.js.map +1 -0
  55. package/dist/agent-runtime/models/base.d.ts +3 -1
  56. package/dist/agent-runtime/models/client.js +30 -9
  57. package/dist/agent-runtime/models/client.js.map +1 -1
  58. package/dist/agent-runtime/models/reasoning.d.ts +10 -0
  59. package/dist/agent-runtime/models/reasoning.js +29 -0
  60. package/dist/agent-runtime/models/reasoning.js.map +1 -0
  61. package/dist/agent-runtime/models/runtime-registry.d.ts +2 -1
  62. package/dist/agent-runtime/models/runtime-registry.js +4 -2
  63. package/dist/agent-runtime/models/runtime-registry.js.map +1 -1
  64. package/dist/agent-runtime/models/vlm/autoglm.js +6 -2
  65. package/dist/agent-runtime/models/vlm/autoglm.js.map +1 -1
  66. package/dist/agent-runtime/models/vlm/base.js +1 -12
  67. package/dist/agent-runtime/models/vlm/base.js.map +1 -1
  68. package/dist/agent-runtime/models/vlm/index.d.ts +0 -4
  69. package/dist/agent-runtime/models/vlm/index.js +0 -4
  70. package/dist/agent-runtime/models/vlm/index.js.map +1 -1
  71. package/dist/agent-runtime/modes/hybrid/index.d.ts +2 -12
  72. package/dist/agent-runtime/modes/hybrid/index.js +10 -27
  73. package/dist/agent-runtime/modes/hybrid/index.js.map +1 -1
  74. package/dist/agent-runtime/modes/hybrid/planner/base.d.ts +4 -44
  75. package/dist/agent-runtime/modes/hybrid/planner/base.js +3 -21
  76. package/dist/agent-runtime/modes/hybrid/planner/base.js.map +1 -1
  77. package/dist/agent-runtime/modes/hybrid/planner/llm-planner.d.ts +1 -1
  78. package/dist/agent-runtime/modes/hybrid/planner/llm-planner.js +9 -6
  79. package/dist/agent-runtime/modes/hybrid/planner/llm-planner.js.map +1 -1
  80. package/dist/agent-runtime/modes/hybrid/planner/prompts.d.ts +1 -17
  81. package/dist/agent-runtime/modes/hybrid/planner/prompts.js +65 -160
  82. package/dist/agent-runtime/modes/hybrid/planner/prompts.js.map +1 -1
  83. package/dist/agent-runtime/plan-executor.d.ts +13 -14
  84. package/dist/agent-runtime/plan-executor.js +236 -122
  85. package/dist/agent-runtime/plan-executor.js.map +1 -1
  86. package/dist/agent-runtime/pro-memory.js +1 -1
  87. package/dist/agent-runtime/pro-memory.js.map +1 -1
  88. package/dist/agent-runtime/pro-operator.js +8 -2
  89. package/dist/agent-runtime/pro-operator.js.map +1 -1
  90. package/dist/agent-runtime/pro-perception.d.ts +1 -0
  91. package/dist/agent-runtime/pro-perception.js +4 -2
  92. package/dist/agent-runtime/pro-perception.js.map +1 -1
  93. package/dist/agent-runtime/pro-target-check.d.ts +2 -2
  94. package/dist/agent-runtime/pro-target-check.js +102 -17
  95. package/dist/agent-runtime/pro-target-check.js.map +1 -1
  96. package/dist/agent-runtime/prompts/operator.d.ts +2 -0
  97. package/dist/agent-runtime/prompts/operator.js +69 -0
  98. package/dist/agent-runtime/prompts/operator.js.map +1 -0
  99. package/dist/agent-runtime/prompts/verification.d.ts +2 -0
  100. package/dist/agent-runtime/prompts/verification.js +52 -0
  101. package/dist/agent-runtime/prompts/verification.js.map +1 -0
  102. package/dist/agent-runtime/skills/replay.d.ts +4 -14
  103. package/dist/agent-runtime/skills/replay.js +46 -27
  104. package/dist/agent-runtime/skills/replay.js.map +1 -1
  105. package/dist/agent-runtime/tool-batch.d.ts +10 -0
  106. package/dist/agent-runtime/tool-batch.js +51 -0
  107. package/dist/agent-runtime/tool-batch.js.map +1 -0
  108. package/dist/agent-runtime/visual-state.d.ts +2 -0
  109. package/dist/agent-runtime/visual-state.js +24 -0
  110. package/dist/agent-runtime/visual-state.js.map +1 -0
  111. package/dist/agent-runtime/vlm.d.ts +1 -1
  112. package/dist/agent-runtime/vlm.js +1 -1
  113. package/dist/api.js +3 -1
  114. package/dist/api.js.map +1 -1
  115. package/dist/app/agent-host.js +6 -2
  116. package/dist/app/agent-host.js.map +1 -1
  117. package/dist/app/multi-device-host.js +4 -5
  118. package/dist/app/multi-device-host.js.map +1 -1
  119. package/dist/app/session-manager.d.ts +3 -45
  120. package/dist/app/session-manager.js +8 -9
  121. package/dist/app/session-manager.js.map +1 -1
  122. package/dist/bootstrap.js +25 -84
  123. package/dist/bootstrap.js.map +1 -1
  124. package/dist/config/loader.js +12 -1
  125. package/dist/config/loader.js.map +1 -1
  126. package/dist/config/schema.d.ts +7 -2
  127. package/dist/config/schema.js +8 -5
  128. package/dist/config/schema.js.map +1 -1
  129. package/dist/core/abortable-read.d.ts +1 -0
  130. package/dist/core/abortable-read.js +24 -0
  131. package/dist/core/abortable-read.js.map +1 -0
  132. package/dist/device/ui-xml.d.ts +4 -0
  133. package/dist/device/ui-xml.js +35 -13
  134. package/dist/device/ui-xml.js.map +1 -1
  135. package/dist/fleet/adapters/process/node-process-manager.d.ts +2 -1
  136. package/dist/fleet/adapters/process/node-process-manager.js +11 -19
  137. package/dist/fleet/adapters/process/node-process-manager.js.map +1 -1
  138. package/dist/inbound/acp/handlers/models.js +11 -8
  139. package/dist/inbound/acp/handlers/models.js.map +1 -1
  140. package/dist/inbound/acp/handlers/session.d.ts +1 -4
  141. package/dist/inbound/acp/handlers/session.js +3 -6
  142. package/dist/inbound/acp/handlers/session.js.map +1 -1
  143. package/dist/inbound/bma/agent.d.ts +47 -0
  144. package/dist/inbound/bma/agent.js +130 -0
  145. package/dist/inbound/bma/agent.js.map +1 -0
  146. package/dist/inbound/bma/command.d.ts +15 -0
  147. package/dist/inbound/bma/command.js +98 -0
  148. package/dist/inbound/bma/command.js.map +1 -0
  149. package/dist/inbound/bma/model-proxy.d.ts +22 -0
  150. package/dist/inbound/bma/model-proxy.js +130 -0
  151. package/dist/inbound/bma/model-proxy.js.map +1 -0
  152. package/dist/inbound/cli/main.js +12 -2
  153. package/dist/inbound/cli/main.js.map +1 -1
  154. package/dist/inbound/cli/run-artifacts.js +16 -7
  155. package/dist/inbound/cli/run-artifacts.js.map +1 -1
  156. package/dist/inbound/cli/run-command.d.ts +7 -0
  157. package/dist/inbound/cli/run-command.js +37 -29
  158. package/dist/inbound/cli/run-command.js.map +1 -1
  159. package/dist/index.d.ts +1 -10
  160. package/dist/index.js +1 -6
  161. package/dist/index.js.map +1 -1
  162. package/dist/mcp-client/autospawn.d.ts +7 -9
  163. package/dist/mcp-client/autospawn.js +16 -12
  164. package/dist/mcp-client/autospawn.js.map +1 -1
  165. package/dist/mcp-client/client.d.ts +6 -3
  166. package/dist/mcp-client/client.js +31 -13
  167. package/dist/mcp-client/client.js.map +1 -1
  168. package/dist/mcp-client/runtime.d.ts +5 -2
  169. package/dist/plugins/agent-sync/index.d.ts +32 -17
  170. package/dist/plugins/agent-sync/index.js +223 -232
  171. package/dist/plugins/agent-sync/index.js.map +1 -1
  172. package/dist/plugins/message-bus/inbound.d.ts +0 -32
  173. package/dist/plugins/message-bus/inbound.js +3 -0
  174. package/dist/plugins/message-bus/inbound.js.map +1 -1
  175. package/dist/plugins/message-bus/ms-channel-notifier.d.ts +1 -0
  176. package/dist/plugins/message-bus/ms-channel-notifier.js +16 -8
  177. package/dist/plugins/message-bus/ms-channel-notifier.js.map +1 -1
  178. package/dist/plugins/native-chat/deadline.d.ts +2 -0
  179. package/dist/plugins/native-chat/deadline.js +16 -0
  180. package/dist/plugins/native-chat/deadline.js.map +1 -0
  181. package/dist/plugins/native-chat/index.d.ts +46 -0
  182. package/dist/plugins/native-chat/index.js +331 -0
  183. package/dist/plugins/native-chat/index.js.map +1 -0
  184. package/dist/plugins/native-chat/reply-stream.d.ts +50 -0
  185. package/dist/plugins/native-chat/reply-stream.js +81 -0
  186. package/dist/plugins/native-chat/reply-stream.js.map +1 -0
  187. package/dist/plugins/native-chat/session-notifier.d.ts +16 -0
  188. package/dist/plugins/native-chat/session-notifier.js +130 -0
  189. package/dist/plugins/native-chat/session-notifier.js.map +1 -0
  190. package/dist/ports/agent-core.d.ts +2 -0
  191. package/dist/ports/agent-host.d.ts +2 -0
  192. package/dist/ports/agent-host.js +0 -21
  193. package/dist/ports/agent-host.js.map +1 -1
  194. package/dist/ports/device.d.ts +0 -1
  195. package/dist/ports/device.js +0 -1
  196. package/dist/ports/device.js.map +1 -1
  197. package/dist/runtime-authority/device-prompt.gen.d.ts +54 -0
  198. package/dist/runtime-authority/device-prompt.gen.js +19 -0
  199. package/dist/runtime-authority/device-prompt.gen.js.map +1 -0
  200. package/dist/types.d.ts +10 -0
  201. package/dist/types.js +1 -0
  202. package/dist/types.js.map +1 -1
  203. package/package.json +5 -3
  204. package/dist/adapters/device-desktop.d.ts +0 -49
  205. package/dist/adapters/device-desktop.js +0 -132
  206. package/dist/adapters/device-desktop.js.map +0 -1
  207. package/dist/agent-runtime/factory.d.ts +0 -24
  208. package/dist/agent-runtime/factory.js +0 -25
  209. package/dist/agent-runtime/factory.js.map +0 -1
  210. package/dist/agent-runtime/models/vlm/guiowl.d.ts +0 -1
  211. package/dist/agent-runtime/models/vlm/guiowl.js +0 -249
  212. package/dist/agent-runtime/models/vlm/guiowl.js.map +0 -1
  213. package/dist/agent-runtime/models/vlm/maiui.d.ts +0 -1
  214. package/dist/agent-runtime/models/vlm/maiui.js +0 -142
  215. package/dist/agent-runtime/models/vlm/maiui.js.map +0 -1
  216. package/dist/agent-runtime/models/vlm/qwen38.d.ts +0 -1
  217. package/dist/agent-runtime/models/vlm/qwen38.js +0 -241
  218. package/dist/agent-runtime/models/vlm/qwen38.js.map +0 -1
  219. package/dist/agent-runtime/models/vlm/qwenvl.d.ts +0 -1
  220. package/dist/agent-runtime/models/vlm/qwenvl.js +0 -202
  221. package/dist/agent-runtime/models/vlm/qwenvl.js.map +0 -1
  222. package/dist/agent-runtime/modes/orchestrator/app-guide.js.map +0 -1
  223. package/dist/agent-runtime/modes/orchestrator/index.d.ts +0 -41
  224. package/dist/agent-runtime/modes/orchestrator/index.js +0 -218
  225. package/dist/agent-runtime/modes/orchestrator/index.js.map +0 -1
  226. package/dist/agent-runtime/modes/orchestrator/prompt.d.ts +0 -7
  227. package/dist/agent-runtime/modes/orchestrator/prompt.js +0 -53
  228. package/dist/agent-runtime/modes/orchestrator/prompt.js.map +0 -1
  229. package/dist/agent-runtime/reflection.d.ts +0 -100
  230. package/dist/agent-runtime/reflection.js +0 -241
  231. package/dist/agent-runtime/reflection.js.map +0 -1
  232. package/dist/agent-runtime/reflectors/action-reflector.d.ts +0 -97
  233. package/dist/agent-runtime/reflectors/action-reflector.js +0 -234
  234. package/dist/agent-runtime/reflectors/action-reflector.js.map +0 -1
  235. package/dist/agent-runtime/reflectors/policy.d.ts +0 -56
  236. package/dist/agent-runtime/reflectors/policy.js +0 -111
  237. package/dist/agent-runtime/reflectors/policy.js.map +0 -1
  238. package/dist/agent-runtime/reflectors/trajectory-reflector.d.ts +0 -93
  239. package/dist/agent-runtime/reflectors/trajectory-reflector.js +0 -224
  240. package/dist/agent-runtime/reflectors/trajectory-reflector.js.map +0 -1
@@ -4,19 +4,36 @@
4
4
  * Each attached device owns an independent logical runtime lease even when
5
5
  * multiple device runtimes share one OS process. Lease credentials remain
6
6
  * memory-only and are never included in logs.
7
+ *
8
+ * Registration, heartbeat and lease fencing are owned by the harness SDK's
9
+ * `RuntimeRegistrationCoordinator`; this plugin keeps only the durable
10
+ * registration lineage and the Types4 agent projection it publishes.
7
11
  */
8
12
  import { randomUUID } from "node:crypto";
9
13
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
10
14
  import { dirname, join } from "node:path";
11
- import { heartbeatRuntime, registerRuntime, syncAgents, } from "../../adapters/agent-gateway-client.js";
15
+ import { AgentGatewayClient, InstanceAuthorityRuntimeRegistrationTransport, RuntimeRegistrationCoordinator, RuntimeRegistrationJournalAdvanceRequiredError, RuntimeRegistrationOwnerResetRequiredError, signAgentMessage, } from "@beeos-ai/cloud-harness-sdk";
16
+ import { syncAgents, } from "../../adapters/agent-gateway-client.js";
12
17
  import { AGENT_PROJECTION_HASH_DOMAIN, DEVICE_RUNTIME_CAPABILITIES, DEVICE_RUNTIME_CONTRACT_REVISION, DEVICE_RUNTIME_METHODS, DEVICE_RUNTIME_RPC_PROTOCOL_VERSION, verifiedAdmittedManifestDigest, } from "../../runtime-authority/contract.js";
13
18
  import { runtimeDomainHash, sha256Hex, } from "../../runtime-authority/canonical.js";
19
+ import { DEVICE_NATIVE_MANIFEST_DIGEST, DEVICE_NATIVE_RUNTIME_MANIFEST } from "../../runtime-authority/device-prompt.gen.js";
14
20
  const REGISTER_RETRY_MS = 5_000;
15
21
  const HANDOFF_RETRY_MS = 65_000;
16
22
  const SYNC_RETRY_MS = 5_000;
17
23
  const REGISTRATION_INTENT_HASH_DOMAIN = "beeos.device-agent.registration-intent.v1";
18
- export function resolveRegistrationManifestDigest() {
19
- return verifiedAdmittedManifestDigest();
24
+ /**
25
+ * A `fenced` registration is recoverable once the predecessor's control-plane
26
+ * lease settles. Keep the pre-SDK 65 s handoff wait instead of the SDK's
27
+ * shorter default so we do not thrash the authority while that lease is live.
28
+ */
29
+ const HANDOFF_BACKOFF = Object.freeze({
30
+ maxAttempts: Number.POSITIVE_INFINITY,
31
+ initialDelayMs: HANDOFF_RETRY_MS,
32
+ maxDelayMs: HANDOFF_RETRY_MS,
33
+ factor: 1,
34
+ });
35
+ export function resolveRegistrationManifestDigest(cloudTarget = false) {
36
+ return cloudTarget ? DEVICE_NATIVE_MANIFEST_DIGEST : verifiedAdmittedManifestDigest();
20
37
  }
21
38
  /**
22
39
  * Cluster treats registrationId as an idempotency key for one immutable
@@ -29,37 +46,52 @@ export function buildRuntimeRegistrationId(identityFingerprint, manifestDigest,
29
46
  return `device-agent-${identityFingerprint.slice(0, 16)}-${generationHash.slice(0, 24)}`;
30
47
  }
31
48
  function registrationIntentHash(manifestDigest) {
49
+ const native = manifestDigest === DEVICE_NATIVE_MANIFEST_DIGEST;
32
50
  return runtimeDomainHash(REGISTRATION_INTENT_HASH_DOMAIN, {
33
51
  contractRevision: DEVICE_RUNTIME_CONTRACT_REVISION,
34
52
  manifestDigest,
35
53
  runtimeRpcProtocolVersion: DEVICE_RUNTIME_RPC_PROTOCOL_VERSION,
36
- runtimeMethods: DEVICE_RUNTIME_METHODS,
37
- capabilities: DEVICE_RUNTIME_CAPABILITIES,
54
+ runtimeMethods: native ? DEVICE_NATIVE_RUNTIME_MANIFEST.runtimeMethods : DEVICE_RUNTIME_METHODS,
55
+ capabilities: native ? DEVICE_NATIVE_RUNTIME_MANIFEST.capabilities : DEVICE_RUNTIME_CAPABILITIES,
38
56
  });
39
57
  }
40
- export function parseExpectedJournalGeneration(error) {
41
- const match = `${error.message}\n${error.body ?? ""}`.match(/expected=([1-9][0-9]*)/);
42
- return match?.[1] ?? null;
58
+ /** Adapt the bridge-client keypair into the harness SDK request identity. */
59
+ function requestIdentity(identity) {
60
+ return {
61
+ publicKey: identity.publicKey,
62
+ sign: (message) => signAgentMessage(message, identity.privateKey),
63
+ };
43
64
  }
44
65
  export class AgentSyncPlugin {
45
66
  opts;
46
67
  name = "agent-sync";
47
68
  ctx;
48
69
  stopped = true;
49
- generation = 0;
50
- controlTimer = null;
51
- expiryTimer = null;
52
- syncTimer = null;
53
70
  periodicSync = null;
54
- lease = null;
71
+ restartTimer = null;
72
+ syncTimer = null;
73
+ coordinator = null;
55
74
  identity = null;
56
75
  registration = null;
57
76
  acceptedRevision = 0;
58
77
  acceptedHash = "";
78
+ syncedEpoch = null;
79
+ initializing = false;
59
80
  queue = Promise.resolve();
81
+ authorityListeners = new Set();
60
82
  constructor(opts) {
61
83
  this.opts = opts;
62
84
  }
85
+ currentLease() {
86
+ const lease = this.coordinator?.authorityLease;
87
+ if (!lease || !this.registration)
88
+ return null;
89
+ return { ...lease, journalStoreId: this.registration.journalStoreId, journalGeneration: this.registration.journalGeneration };
90
+ }
91
+ subscribeAuthority(listener) {
92
+ this.authorityListeners.add(listener);
93
+ return () => this.authorityListeners.delete(listener);
94
+ }
63
95
  init(ctx) {
64
96
  this.ctx = ctx;
65
97
  }
@@ -67,27 +99,34 @@ export class AgentSyncPlugin {
67
99
  if (!this.ctx || this.ctx.config.standalone)
68
100
  return;
69
101
  this.stopped = false;
70
- const generation = ++this.generation;
71
- await this.enqueue(() => this.registerOnce(generation));
102
+ await this.enqueue(() => this.startRegistration());
103
+ await this.drain();
72
104
  const interval = this.opts.intervalMs ??
73
- Number(process.env.BEEOS_AGENT_SYNC_INTERVAL_MS ?? "0");
105
+ Number(process.env.BEEOS_AGENT_SYNC_INTERVAL_MS ?? "30000");
74
106
  if (interval > 0) {
75
107
  this.periodicSync = setInterval(() => {
76
- void this.enqueue(() => this.syncProjection(generation, false));
108
+ void this.enqueue(() => this.syncProjection(false));
77
109
  }, interval);
78
110
  this.periodicSync.unref?.();
79
111
  }
80
112
  }
81
113
  async stop() {
82
114
  this.stopped = true;
83
- this.generation++;
84
115
  this.clearTimers();
85
- this.lease = null;
116
+ this.coordinator?.stop();
117
+ this.coordinator = null;
118
+ for (const listener of this.authorityListeners)
119
+ listener();
86
120
  this.identity = null;
121
+ this.syncedEpoch = null;
87
122
  await this.queue.catch(() => undefined);
88
123
  }
89
- async registerOnce(generation) {
90
- if (!this.isCurrent(generation) || !this.ctx)
124
+ /**
125
+ * Build the harness registration coordinator for the current durable
126
+ * lineage and let it own registration, heartbeat and lease fencing.
127
+ */
128
+ async startRegistration() {
129
+ if (!this.isCurrent() || !this.ctx)
91
130
  return;
92
131
  const { config, log } = this.ctx;
93
132
  if (!config.agentGatewayUrl)
@@ -95,13 +134,13 @@ export class AgentSyncPlugin {
95
134
  const identity = await this.opts.loadIdentity().catch(() => null);
96
135
  if (!identity) {
97
136
  log.warn("runtime-authority: identity unavailable; retrying registration");
98
- this.scheduleRegister(REGISTER_RETRY_MS, generation);
137
+ this.scheduleRestart(REGISTER_RETRY_MS);
99
138
  return;
100
139
  }
101
140
  this.identity = identity;
102
141
  let manifestDigest;
103
142
  try {
104
- manifestDigest = resolveRegistrationManifestDigest();
143
+ manifestDigest = resolveRegistrationManifestDigest(Boolean(config.cloudTargetInstanceId));
105
144
  }
106
145
  catch (error) {
107
146
  // A defective contract cannot heal by retrying, and the gateway rejects it
@@ -111,138 +150,123 @@ export class AgentSyncPlugin {
111
150
  }
112
151
  const fingerprint = sha256Hex(identity.publicKey);
113
152
  const registration = await this.ensureRegistrationState(fingerprint, manifestDigest);
114
- const payload = {
153
+ const gateway = new AgentGatewayClient({
154
+ baseUrl: config.agentGatewayUrl,
155
+ identity: requestIdentity(identity),
156
+ });
157
+ const transport = new InstanceAuthorityRuntimeRegistrationTransport({
158
+ gateway,
159
+ registrationId: registration.registrationId,
160
+ });
161
+ const coordinator = new RuntimeRegistrationCoordinator(transport, {
115
162
  registrationId: registration.registrationId,
116
163
  handlerIdentity: `device-agent:${fingerprint.slice(0, 32)}`,
117
164
  contractRevision: DEVICE_RUNTIME_CONTRACT_REVISION,
118
- manifestDigest,
119
- runtimeRpcProtocolVersion: DEVICE_RUNTIME_RPC_PROTOCOL_VERSION,
120
- runtimeMethods: [...DEVICE_RUNTIME_METHODS],
121
- capabilities: [...DEVICE_RUNTIME_CAPABILITIES],
165
+ runtimeMethods: (config.cloudTargetInstanceId ? DEVICE_NATIVE_RUNTIME_MANIFEST.runtimeMethods : DEVICE_RUNTIME_METHODS),
166
+ capabilities: (config.cloudTargetInstanceId ? DEVICE_NATIVE_RUNTIME_MANIFEST.capabilities : DEVICE_RUNTIME_CAPABILITIES),
167
+ manifestDigest: manifestDigest,
122
168
  journalStoreId: registration.journalStoreId,
123
169
  journalGeneration: registration.journalGeneration,
124
- };
170
+ instanceIdentityKeyId: `device:${fingerprint}`,
171
+ targetInstanceId: config.cloudTargetInstanceId,
172
+ signIdentityProof: (message) => signAgentMessage(message, identity.privateKey),
173
+ fencedRetry: HANDOFF_BACKOFF,
174
+ }, (reason, cause) => this.onLeaseLost(reason, cause), () => this.now());
175
+ const previous = this.coordinator;
176
+ this.coordinator = coordinator;
177
+ previous?.stop();
178
+ coordinator.subscribeAuthority((lease) => this.onAuthorityChanged(lease));
179
+ this.initializing = true;
180
+ let lease;
125
181
  try {
126
- const result = await registerRuntime({
127
- agentGatewayUrl: config.agentGatewayUrl,
128
- identity,
129
- payload,
130
- fetchImpl: this.opts.fetchImpl,
131
- now: this.opts.now,
132
- randomUUID: this.opts.randomUUID,
133
- });
134
- if (!this.isCurrent(generation))
135
- return;
136
- if (result.status !== "active" ||
137
- !result.instanceId ||
138
- !result.leaseId ||
139
- !result.runtimeLeaseCredential ||
140
- !result.leaseExpiresAt ||
141
- !result.heartbeatIntervalMs ||
142
- !result.runtimeEpoch) {
143
- log.warn({ status: result.status, activeRuntimeEpoch: result.activeRuntimeEpoch }, "runtime-authority: registration fenced; waiting for handoff");
144
- this.dropLease();
145
- this.scheduleRegister(HANDOFF_RETRY_MS, generation);
146
- return;
147
- }
148
- if (Date.parse(result.leaseExpiresAt) <= this.now()) {
149
- throw new Error("runtime authority returned an expired lease");
150
- }
151
- const sameEpoch = this.lease?.runtimeEpoch === result.runtimeEpoch;
152
- this.lease = {
153
- instanceId: result.instanceId,
154
- leaseId: result.leaseId,
155
- runtimeEpoch: result.runtimeEpoch,
156
- runtimeLeaseCredential: result.runtimeLeaseCredential,
157
- leaseExpiresAt: result.leaseExpiresAt,
158
- heartbeatIntervalMs: result.heartbeatIntervalMs,
159
- };
160
- if (!sameEpoch) {
161
- this.acceptedRevision = 0;
162
- this.acceptedHash = "";
163
- }
164
- this.scheduleExpiry(generation);
165
- log.info({
166
- runtimeEpoch: this.lease.runtimeEpoch,
167
- leaseExpiresAt: this.lease.leaseExpiresAt,
168
- }, "runtime-authority: active lease acquired");
169
- await this.syncProjection(generation, true);
170
- this.scheduleHeartbeat(this.lease.heartbeatIntervalMs, generation);
182
+ lease = await coordinator.start();
171
183
  }
172
- catch (error) {
173
- if (!this.isCurrent(generation))
174
- return;
175
- const gatewayError = error;
176
- const expectedGeneration = parseExpectedJournalGeneration(gatewayError);
177
- const rolled = expectedGeneration
178
- ? await this.rollRegistrationState(fingerprint, manifestDigest, expectedGeneration)
179
- : false;
180
- const retryMs = rolled
181
- ? 0
182
- : gatewayError.status === 409
183
- ? HANDOFF_RETRY_MS
184
- : REGISTER_RETRY_MS;
185
- log.warn({
186
- err: gatewayError.message,
187
- status: gatewayError.status,
188
- retryMs,
189
- ...(rolled ? { journalGeneration: expectedGeneration } : {}),
190
- }, "runtime-authority: registration failed");
191
- this.dropLease();
192
- this.scheduleRegister(retryMs, generation);
184
+ finally {
185
+ this.initializing = false;
186
+ }
187
+ if (!this.isCurrent()) {
188
+ coordinator.stop();
189
+ return;
190
+ }
191
+ if (lease) {
192
+ // The listener is muted while the coordinator starts; publish the first
193
+ // projection here so callers can rely on start() having registered.
194
+ this.syncedEpoch = lease.runtimeEpoch;
195
+ log.info({ runtimeEpoch: lease.runtimeEpoch, leaseExpiresAt: lease.leaseExpiresAt }, "runtime-authority: active lease acquired");
196
+ await this.syncProjection(true);
193
197
  }
194
198
  }
195
- async heartbeat(generation) {
196
- if (!this.isCurrent(generation) || !this.ctx || !this.identity || !this.lease)
199
+ /** Only a new runtime epoch needs a fresh projection; renewals do not. */
200
+ onAuthorityChanged(lease) {
201
+ for (const listener of this.authorityListeners)
202
+ listener();
203
+ if (!lease) {
204
+ this.syncedEpoch = null;
205
+ this.acceptedRevision = 0;
206
+ this.acceptedHash = "";
197
207
  return;
198
- const active = this.lease;
199
- try {
200
- const result = await heartbeatRuntime({
201
- agentGatewayUrl: this.ctx.config.agentGatewayUrl,
202
- identity: this.identity,
203
- registrationId: this.registrationId(),
204
- payload: {
205
- leaseId: active.leaseId,
206
- handlerIdentity: this.handlerIdentity(this.identity),
207
- runtimeEpoch: active.runtimeEpoch,
208
- },
209
- fetchImpl: this.opts.fetchImpl,
210
- now: this.opts.now,
211
- randomUUID: this.opts.randomUUID,
212
- });
213
- if (!this.isCurrent(generation) || this.lease !== active)
214
- return;
215
- if (result.status !== "renewed" ||
216
- !result.runtimeLeaseCredential ||
217
- !result.leaseExpiresAt) {
218
- this.ctx.log.warn({ status: result.status, activeRuntimeEpoch: result.activeRuntimeEpoch }, "runtime-authority: heartbeat fenced");
219
- this.dropLease();
220
- this.scheduleRegister(REGISTER_RETRY_MS, generation);
221
- return;
222
- }
223
- this.lease = {
224
- ...active,
225
- runtimeLeaseCredential: result.runtimeLeaseCredential,
226
- leaseExpiresAt: result.leaseExpiresAt,
227
- };
228
- this.scheduleExpiry(generation);
229
- this.scheduleHeartbeat(active.heartbeatIntervalMs, generation);
230
208
  }
231
- catch (error) {
232
- if (!this.isCurrent(generation) || this.lease !== active)
233
- return;
234
- this.ctx.log.warn({ err: error.message }, "runtime-authority: heartbeat transport failure");
235
- if (Date.parse(active.leaseExpiresAt) > this.now()) {
236
- this.scheduleHeartbeat(Math.min(active.heartbeatIntervalMs, REGISTER_RETRY_MS), generation);
237
- }
238
- else {
239
- this.dropLease();
240
- this.scheduleRegister(REGISTER_RETRY_MS, generation);
241
- }
209
+ if (this.initializing)
210
+ return;
211
+ if (lease.runtimeEpoch === this.syncedEpoch)
212
+ return;
213
+ this.syncedEpoch = lease.runtimeEpoch;
214
+ this.acceptedRevision = 0;
215
+ this.acceptedHash = "";
216
+ void this.enqueue(() => this.syncProjection(true));
217
+ }
218
+ onLeaseLost(reason, cause) {
219
+ this.syncedEpoch = null;
220
+ this.acceptedRevision = 0;
221
+ this.acceptedHash = "";
222
+ const log = this.ctx?.log;
223
+ if (cause instanceof RuntimeRegistrationJournalAdvanceRequiredError) {
224
+ log?.warn({ expectedJournalGeneration: cause.expectedJournalGeneration }, "runtime-authority: journal generation must advance");
225
+ void this.enqueue(() => this.rollAndRestart(cause.expectedJournalGeneration));
226
+ return;
227
+ }
228
+ if (cause instanceof RuntimeRegistrationOwnerResetRequiredError) {
229
+ log?.error("runtime-authority: journal store owner reset required; registration is fenced until an operator resets the journal identity");
230
+ return;
231
+ }
232
+ if (reason === "expired") {
233
+ log?.warn("runtime-authority: lease expired; advancing journal generation");
234
+ void this.enqueue(() => this.rollAndRestart());
235
+ return;
236
+ }
237
+ // Fenced and transient failures are already retried by the coordinator.
238
+ log?.warn({ reason, err: cause?.message }, "runtime-authority: lease lost; coordinator will retry registration");
239
+ }
240
+ /**
241
+ * The authority fenced the old journal lineage. Roll the durable generation
242
+ * exactly once, then rebuild the coordinator so it re-registers the new id.
243
+ */
244
+ async rollAndRestart(expectedGeneration) {
245
+ if (!this.isCurrent() || !this.ctx || !this.identity || !this.registration) {
246
+ return;
242
247
  }
248
+ const manifestDigest = resolveRegistrationManifestDigest();
249
+ const fingerprint = sha256Hex(this.identity.publicKey);
250
+ const current = this.registration;
251
+ const target = expectedGeneration ?? (BigInt(current.journalGeneration) + 1n).toString();
252
+ if (BigInt(target) <= BigInt(current.journalGeneration)) {
253
+ // Already at the requested generation; wait out the predecessor lease.
254
+ this.scheduleRestart(HANDOFF_RETRY_MS);
255
+ return;
256
+ }
257
+ await this.publishRegistrationState({
258
+ ...current,
259
+ registrationId: buildRuntimeRegistrationId(fingerprint, manifestDigest, target, this.nextUUID()),
260
+ journalGeneration: target,
261
+ });
262
+ this.scheduleRestart(0);
243
263
  }
244
- async syncProjection(generation, force, allowConflictRetry = true) {
245
- if (!this.isCurrent(generation) || !this.ctx || !this.identity || !this.lease)
264
+ async syncProjection(force, allowConflictRetry = true) {
265
+ if (!this.isCurrent() || !this.ctx || !this.identity || !this.coordinator) {
266
+ return;
267
+ }
268
+ const lease = this.coordinator.lease;
269
+ if (!lease)
246
270
  return;
247
271
  const declarations = await this.opts.buildDeclarations().catch((error) => {
248
272
  this.ctx.log.warn({ err: error.message }, "agent-sync: declaration build failed");
@@ -258,37 +282,35 @@ export class AgentSyncPlugin {
258
282
  if (!force && canonicalHash === this.acceptedHash)
259
283
  return;
260
284
  const revision = this.acceptedRevision + 1;
261
- const active = this.lease;
262
- const snapshotId = `device-${active.runtimeEpoch}-${revision}-${canonicalHash.slice(0, 16)}`;
285
+ const snapshotId = `device-${lease.runtimeEpoch}-${revision}-${canonicalHash.slice(0, 16)}`;
263
286
  try {
264
287
  const result = await syncAgents({
265
288
  agentGatewayUrl: this.ctx.config.agentGatewayUrl,
266
289
  identity: this.identity,
267
290
  request: {
268
- runtimeEpoch: active.runtimeEpoch,
291
+ runtimeEpoch: lease.runtimeEpoch,
269
292
  revision: String(revision),
270
293
  snapshotId,
271
294
  canonicalHash,
272
295
  agentsComplete: true,
273
296
  skillsComplete: true,
274
297
  agents,
275
- runtimeLeaseCredential: active.runtimeLeaseCredential,
298
+ runtimeLeaseCredential: lease.runtimeLeaseCredential,
276
299
  },
277
300
  fetchImpl: this.opts.fetchImpl,
278
301
  now: this.opts.now,
279
302
  randomUUID: this.opts.randomUUID,
280
303
  });
281
- if (!this.isCurrent(generation) || this.lease !== active)
304
+ if (!this.isCurrent())
282
305
  return;
283
306
  if (!result.accepted) {
284
307
  const acceptedRevision = Number(result.revision);
285
- if (result.runtimeEpoch !== active.runtimeEpoch) {
308
+ if (result.runtimeEpoch !== lease.runtimeEpoch) {
286
309
  this.ctx.log.warn({
287
- runtimeEpoch: active.runtimeEpoch,
310
+ runtimeEpoch: lease.runtimeEpoch,
288
311
  acceptedRuntimeEpoch: result.runtimeEpoch,
289
312
  }, "agent-sync: projection fenced by a newer runtime epoch");
290
- this.dropLease();
291
- this.scheduleRegister(REGISTER_RETRY_MS, generation);
313
+ this.restartRegistration();
292
314
  return;
293
315
  }
294
316
  if (Number.isSafeInteger(acceptedRevision) && acceptedRevision > 0) {
@@ -297,10 +319,10 @@ export class AgentSyncPlugin {
297
319
  }
298
320
  if (result.canonicalHash !== canonicalHash) {
299
321
  if (allowConflictRetry) {
300
- await this.syncProjection(generation, true, false);
322
+ await this.syncProjection(true, false);
301
323
  }
302
324
  else {
303
- this.scheduleSyncRetry(generation);
325
+ this.scheduleSyncRetry();
304
326
  }
305
327
  }
306
328
  return;
@@ -311,21 +333,28 @@ export class AgentSyncPlugin {
311
333
  created: result.created,
312
334
  updated: result.updated,
313
335
  deactivated: result.deactivated,
314
- runtimeEpoch: active.runtimeEpoch,
336
+ runtimeEpoch: lease.runtimeEpoch,
315
337
  revision: this.acceptedRevision,
316
338
  }, "agent-sync: runtime projection accepted");
317
339
  }
318
340
  catch (error) {
319
- if (!this.isCurrent(generation))
341
+ if (!this.isCurrent())
320
342
  return;
321
343
  const gatewayError = error;
322
344
  this.ctx.log.warn({ err: gatewayError.message, status: gatewayError.status }, "agent-sync: projection failed");
345
+ if (gatewayError.status === 409 && allowConflictRetry) {
346
+ // A restart can reuse a live lease whose earlier process published a
347
+ // different hash at revision 1. Advance the projection, not the lease.
348
+ this.acceptedRevision = Math.max(this.acceptedRevision, Math.trunc(this.now()));
349
+ await this.syncProjection(true, false);
350
+ return;
351
+ }
323
352
  if (gatewayError.status === 401 || gatewayError.status === 409) {
324
- this.dropLease();
325
- this.scheduleRegister(REGISTER_RETRY_MS, generation);
353
+ // The lease credential was rejected; re-register rather than retrying it.
354
+ this.restartRegistration();
326
355
  return;
327
356
  }
328
- this.scheduleSyncRetry(generation);
357
+ this.scheduleSyncRetry();
329
358
  }
330
359
  }
331
360
  toProjection(declarations, identity) {
@@ -339,6 +368,8 @@ export class AgentSyncPlugin {
339
368
  ...(agent.version ? { version: agent.version } : {}),
340
369
  ...(agent.avatarUrl ? { avatarUrl: agent.avatarUrl } : {}),
341
370
  ...(agent.homepage ? { homepage: agent.homepage } : {}),
371
+ ...(this.ctx?.config.vlm?.model?.trim()
372
+ ? { defaultModel: this.ctx.config.vlm.model.trim() } : {}),
342
373
  capabilities: {
343
374
  canvas: agent.capabilities?.canvas === true,
344
375
  terminal: agent.capabilities?.terminal === true,
@@ -363,62 +394,36 @@ export class AgentSyncPlugin {
363
394
  }))
364
395
  .sort((left, right) => left.runtimeAgentUid.localeCompare(right.runtimeAgentUid));
365
396
  }
366
- scheduleRegister(delayMs, generation) {
367
- if (!this.isCurrent(generation))
397
+ restartRegistration() {
398
+ if (!this.isCurrent())
368
399
  return;
369
- if (this.controlTimer)
370
- clearTimeout(this.controlTimer);
371
- this.controlTimer = setTimeout(() => {
372
- this.controlTimer = null;
373
- void this.enqueue(() => this.registerOnce(generation));
374
- }, delayMs);
400
+ this.coordinator?.stop();
401
+ this.coordinator = null;
402
+ this.syncedEpoch = null;
403
+ this.acceptedRevision = 0;
404
+ this.acceptedHash = "";
405
+ this.scheduleRestart(REGISTER_RETRY_MS);
375
406
  }
376
- scheduleHeartbeat(delayMs, generation) {
377
- if (!this.isCurrent(generation))
407
+ scheduleRestart(delayMs) {
408
+ if (!this.isCurrent())
378
409
  return;
379
- if (this.controlTimer)
380
- clearTimeout(this.controlTimer);
381
- this.controlTimer = setTimeout(() => {
382
- this.controlTimer = null;
383
- void this.enqueue(() => this.heartbeat(generation));
410
+ if (this.restartTimer)
411
+ clearTimeout(this.restartTimer);
412
+ this.restartTimer = setTimeout(() => {
413
+ this.restartTimer = null;
414
+ void this.enqueue(() => this.startRegistration());
384
415
  }, delayMs);
385
- this.controlTimer.unref?.();
416
+ this.restartTimer.unref?.();
386
417
  }
387
- scheduleSyncRetry(generation) {
388
- if (!this.isCurrent(generation) || this.syncTimer)
418
+ scheduleSyncRetry() {
419
+ if (!this.isCurrent() || this.syncTimer)
389
420
  return;
390
421
  this.syncTimer = setTimeout(() => {
391
422
  this.syncTimer = null;
392
- void this.enqueue(() => this.syncProjection(generation, true));
423
+ void this.enqueue(() => this.syncProjection(true));
393
424
  }, SYNC_RETRY_MS);
394
425
  this.syncTimer.unref?.();
395
426
  }
396
- scheduleExpiry(generation) {
397
- if (this.expiryTimer)
398
- clearTimeout(this.expiryTimer);
399
- const expected = this.lease;
400
- if (!expected)
401
- return;
402
- const remaining = Math.max(0, Date.parse(expected.leaseExpiresAt) - this.now());
403
- this.expiryTimer = setTimeout(() => {
404
- this.expiryTimer = null;
405
- if (!this.isCurrent(generation) || this.lease !== expected)
406
- return;
407
- this.ctx?.log.warn("runtime-authority: lease expired");
408
- this.dropLease();
409
- this.scheduleRegister(REGISTER_RETRY_MS, generation);
410
- }, remaining);
411
- this.expiryTimer.unref?.();
412
- }
413
- registrationId() {
414
- if (!this.registration) {
415
- throw new Error("runtime registration state is unavailable");
416
- }
417
- return this.registration.registrationId;
418
- }
419
- handlerIdentity(identity) {
420
- return `device-agent:${sha256Hex(identity.publicKey).slice(0, 32)}`;
421
- }
422
427
  async ensureRegistrationState(fingerprint, manifestDigest) {
423
428
  if (this.registration)
424
429
  return this.registration;
@@ -441,18 +446,6 @@ export class AgentSyncPlugin {
441
446
  journalGeneration,
442
447
  });
443
448
  }
444
- async rollRegistrationState(fingerprint, manifestDigest, expectedGeneration) {
445
- const current = await this.ensureRegistrationState(fingerprint, manifestDigest);
446
- if (BigInt(expectedGeneration) <= BigInt(current.journalGeneration)) {
447
- return false;
448
- }
449
- await this.publishRegistrationState({
450
- ...current,
451
- registrationId: buildRuntimeRegistrationId(fingerprint, manifestDigest, expectedGeneration, this.nextUUID()),
452
- journalGeneration: expectedGeneration,
453
- });
454
- return true;
455
- }
456
449
  async readRegistrationState(identityFingerprint) {
457
450
  const file = this.registrationStateFile(identityFingerprint);
458
451
  if (!file)
@@ -496,37 +489,35 @@ export class AgentSyncPlugin {
496
489
  nextUUID() {
497
490
  return this.opts.randomUUID ? this.opts.randomUUID() : randomUUID();
498
491
  }
499
- dropLease() {
500
- this.lease = null;
501
- if (this.expiryTimer)
502
- clearTimeout(this.expiryTimer);
503
- this.expiryTimer = null;
504
- }
505
492
  clearTimers() {
506
- for (const timer of [
507
- this.controlTimer,
508
- this.expiryTimer,
509
- this.syncTimer,
510
- this.periodicSync,
511
- ]) {
493
+ for (const timer of [this.periodicSync, this.restartTimer, this.syncTimer]) {
512
494
  if (timer)
513
495
  clearTimeout(timer);
514
496
  }
515
- this.controlTimer = null;
516
- this.expiryTimer = null;
517
- this.syncTimer = null;
518
497
  this.periodicSync = null;
498
+ this.restartTimer = null;
499
+ this.syncTimer = null;
519
500
  }
520
501
  now() {
521
502
  return this.opts.now ? this.opts.now() : Date.now();
522
503
  }
523
- isCurrent(generation) {
524
- return !this.stopped && generation === this.generation;
504
+ isCurrent() {
505
+ return !this.stopped;
525
506
  }
526
507
  enqueue(work) {
527
508
  const next = this.queue.then(work, work);
528
509
  this.queue = next.catch(() => undefined);
529
510
  return next;
530
511
  }
512
+ /** Wait until no further work is chained onto the serial queue. */
513
+ async drain() {
514
+ let current = this.queue;
515
+ while (true) {
516
+ await current;
517
+ if (this.queue === current)
518
+ return;
519
+ current = this.queue;
520
+ }
521
+ }
531
522
  }
532
523
  //# sourceMappingURL=index.js.map