@cydm/happy-elves 0.1.0-beta.495 → 0.1.0-beta.496

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves-cli",
3
- "version": "0.1.0-beta.495",
3
+ "version": "0.1.0-beta.496",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -742,6 +742,7 @@ class DirectState {
742
742
  acpxVersion: runtimeCapabilities.version,
743
743
  daemonVersion: daemonPackage.version,
744
744
  ...(runtimeCapabilities.turnPrimitives ? { turnPrimitives: runtimeCapabilities.turnPrimitives } : {}),
745
+ runtimeEvents: { forwardCursor: true },
745
746
  sessionPrimitives: runtimePrimitiveSupportForCapabilities(runtimeCapabilities.sessionPrimitives, "native"),
746
747
  ...(runtimeCapabilities.sidechat ? { sidechat: runtimeCapabilities.sidechat } : {}),
747
748
  directoryBrowser: true,
@@ -22,6 +22,7 @@ export declare function catchUpSubscriptionEvents(input: {
22
22
  config: DaemonConfig;
23
23
  subscription: OrchestrationSubscriptionRecord;
24
24
  }): Promise<SubscriptionCatchUpResult>;
25
+ export declare function remoteEventCursorForSubscription(crewMachineId: string, subscription: Pick<OrchestrationSubscriptionRecord, "remoteEventCursor" | "remoteEventSeq">): string | undefined;
25
26
  export declare function attentionFromReconcile(result: TurnReconcileResult): {
26
27
  eventType: TaskAttentionEventType;
27
28
  reason: string;
@@ -4,10 +4,13 @@ import { RemoteCommandError, requestRemoteMachineResult } from "../relay/remote-
4
4
  import { runtimeRead } from "../session/runtime-truth.js";
5
5
  import { activeSubscriptionsForTurn, appendAttention, markSubscriptionLiveness, markSubscriptionRemoteScan, recordTurnRuntimeBinding, } from "./store.js";
6
6
  import { scheduleRemoteOrchestrationWork } from "./remote-work-budget.js";
7
+ import { relayMachineRegistry } from "../state.js";
7
8
  const remoteCatchupsInFlight = new Map();
8
9
  const legacyRemoteCursorFallbackUntil = new Map();
10
+ const remoteForwardCursorConfirmed = new Set();
9
11
  const legacyRemoteCursorFallbackMs = 5 * 60 * 1000;
10
12
  const initialRemoteEventCursor = "event_000000000000";
13
+ const forwardEventCursorSinceBeta = 495;
11
14
  export async function observeSessionEvent(input) {
12
15
  const attention = attentionFromEvent(input.payload);
13
16
  if (!attention) {
@@ -116,11 +119,10 @@ export async function catchUpSubscriptionEvents(input) {
116
119
  }
117
120
  async function catchUpSubscriptionEventsInternal(input) {
118
121
  const crewMachineId = input.subscription.crewMachineId ?? input.config.machineId;
119
- const remoteCursorEnabled = crewMachineId !== input.config.machineId && canUseRemoteEventCursor(crewMachineId);
120
- const remoteCursor = remoteCursorEnabled
121
- ? input.subscription.remoteEventCursor
122
- ?? (input.subscription.remoteEventSeq ? `event_${String(input.subscription.remoteEventSeq).padStart(12, "0")}` : initialRemoteEventCursor)
122
+ const remoteCursor = crewMachineId !== input.config.machineId
123
+ ? remoteEventCursorForSubscription(crewMachineId, input.subscription)
123
124
  : undefined;
125
+ const remoteCursorEnabled = remoteCursor !== undefined;
124
126
  try {
125
127
  const result = crewMachineId === input.config.machineId
126
128
  ? await runtimeRead(input.config, {
@@ -171,7 +173,14 @@ async function catchUpSubscriptionEventsInternal(input) {
171
173
  // the forward cursor in its response. Treat that as the legacy path for
172
174
  // this machine; in particular, never advance a durable cursor from a
173
175
  // page whose peer cannot prove append-only progress.
174
- if (crewMachineId !== input.config.machineId && remoteCursorEnabled && !result.cursorSupported && !result.nextAfter) {
176
+ if (crewMachineId !== input.config.machineId && result.cursorSupported === true) {
177
+ remoteForwardCursorConfirmed.add(crewMachineId);
178
+ }
179
+ else if (crewMachineId !== input.config.machineId && remoteCursorEnabled && !result.nextAfter) {
180
+ // A peer that accepted the forward request but cannot prove the cursor
181
+ // contract is no longer trusted. Remove the positive handshake so the
182
+ // next safe, legacy-compatible request can renegotiate it.
183
+ remoteForwardCursorConfirmed.delete(crewMachineId);
175
184
  legacyRemoteCursorFallbackUntil.set(crewMachineId, Date.now() + legacyRemoteCursorFallbackMs);
176
185
  }
177
186
  const observed = await observeSubscriptionRuntimeEvents({
@@ -201,6 +210,7 @@ async function catchUpSubscriptionEventsInternal(input) {
201
210
  }
202
211
  catch (error) {
203
212
  if (crewMachineId !== input.config.machineId && remoteCursorEnabled && isRemoteEventCursorCompatibilityFailure(error)) {
213
+ remoteForwardCursorConfirmed.delete(crewMachineId);
204
214
  legacyRemoteCursorFallbackUntil.set(crewMachineId, Date.now() + legacyRemoteCursorFallbackMs);
205
215
  }
206
216
  await appendOrchestrationAudit(input.config, "orchestration.subscription.catchup_failed", `Subscription catch-up failed for ${input.subscription.taskId}`, {
@@ -216,13 +226,63 @@ async function catchUpSubscriptionEventsInternal(input) {
216
226
  function canUseRemoteEventCursor(crewMachineId) {
217
227
  const until = legacyRemoteCursorFallbackUntil.get(crewMachineId);
218
228
  if (until === undefined)
219
- return true;
229
+ return remotePeerAdvertisesForwardCursor(crewMachineId);
220
230
  if (until <= Date.now()) {
221
231
  legacyRemoteCursorFallbackUntil.delete(crewMachineId);
222
- return true;
232
+ return remotePeerAdvertisesForwardCursor(crewMachineId);
223
233
  }
224
234
  return false;
225
235
  }
236
+ export function remoteEventCursorForSubscription(crewMachineId, subscription) {
237
+ const fallbackUntil = legacyRemoteCursorFallbackUntil.get(crewMachineId);
238
+ if (fallbackUntil !== undefined && fallbackUntil > Date.now())
239
+ return undefined;
240
+ if (fallbackUntil !== undefined)
241
+ legacyRemoteCursorFallbackUntil.delete(crewMachineId);
242
+ const advertised = remotePeerForwardCursorSupport(crewMachineId);
243
+ // An explicit old version wins over a stale persisted cursor (for example,
244
+ // after a peer downgrade). Unknown manifests are different: a persisted
245
+ // cursor is still positive evidence until the peer identity is refreshed.
246
+ if (advertised === false)
247
+ return undefined;
248
+ // A persisted cursor is positive evidence from a previous successful
249
+ // forward-page response. It remains usable even when the peer's manifest is
250
+ // temporarily unavailable during reconnect.
251
+ if (subscription.remoteEventCursor)
252
+ return subscription.remoteEventCursor;
253
+ if (advertised !== true || !canUseRemoteEventCursor(crewMachineId))
254
+ return undefined;
255
+ return subscription.remoteEventSeq
256
+ ? `event_${String(subscription.remoteEventSeq).padStart(12, "0")}`
257
+ : initialRemoteEventCursor;
258
+ }
259
+ function remotePeerAdvertisesForwardCursor(crewMachineId) {
260
+ return remotePeerForwardCursorSupport(crewMachineId) === true;
261
+ }
262
+ function remotePeerForwardCursorSupport(crewMachineId) {
263
+ const machine = relayMachineRegistry.get(crewMachineId);
264
+ const daemonVersion = machine?.capabilities.daemonVersion?.trim();
265
+ const advertisedFeature = machine?.capabilities.runtimeEvents?.forwardCursor;
266
+ if (advertisedFeature === false)
267
+ return false;
268
+ if (!daemonVersion) {
269
+ if (advertisedFeature === true || remoteForwardCursorConfirmed.has(crewMachineId))
270
+ return true;
271
+ return undefined;
272
+ }
273
+ // Published beta495 introduced the append-only eventsPage `after` field.
274
+ // Workspace daemons also publish an explicit feature bit. The historical
275
+ // package version 0.1.0 alone is not evidence: older development daemons
276
+ // used that same version while rejecting `after`.
277
+ const match = /^0\.1\.0-beta\.(\d+)$/.exec(daemonVersion);
278
+ if (match && Number(match[1]) < forwardEventCursorSinceBeta)
279
+ return false;
280
+ if (advertisedFeature === true || remoteForwardCursorConfirmed.has(crewMachineId))
281
+ return true;
282
+ if (match)
283
+ return Number(match[1]) >= forwardEventCursorSinceBeta;
284
+ return undefined;
285
+ }
226
286
  function isRemoteEventCursorCompatibilityFailure(error) {
227
287
  if (!(error instanceof RemoteCommandError))
228
288
  return false;
@@ -49,6 +49,7 @@ export async function registerMachine(ws, config) {
49
49
  acpxVersion: capabilities.version,
50
50
  daemonVersion: daemonPackage.version,
51
51
  ...(capabilities.turnPrimitives ? { turnPrimitives: capabilities.turnPrimitives } : {}),
52
+ runtimeEvents: { forwardCursor: true },
52
53
  sessionPrimitives: runtimePrimitiveSupportForCapabilities(capabilities.sessionPrimitives, "native"),
53
54
  ...(capabilities.sidechat ? { sidechat: capabilities.sidechat } : {}),
54
55
  directoryBrowser: true,
@@ -45,6 +45,9 @@ export declare function machineList(config: DaemonConfig): {
45
45
  turnPrimitives?: {
46
46
  steer: "native" | "unsupported";
47
47
  };
48
+ runtimeEvents?: {
49
+ forwardCursor: boolean;
50
+ };
48
51
  sidechat?: import("../../../../../packages/shared/dist/index.js").SidechatCapability;
49
52
  directoryBrowser?: boolean;
50
53
  filePreview?: boolean;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves-daemon",
3
- "version": "0.1.0-beta.495",
3
+ "version": "0.1.0-beta.496",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves",
3
- "version": "0.1.0-beta.495",
3
+ "version": "0.1.0-beta.496",
4
4
  "description": "Remote controller for local coding agents with hosted or self-hosted relay support.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -136,6 +136,9 @@ export declare const capabilitiesSchema: z.ZodObject<{
136
136
  unsupported: "unsupported";
137
137
  }>;
138
138
  }, z.core.$strip>>;
139
+ runtimeEvents: z.ZodOptional<z.ZodObject<{
140
+ forwardCursor: z.ZodBoolean;
141
+ }, z.core.$strip>>;
139
142
  sidechat: z.ZodOptional<z.ZodObject<{
140
143
  strategy: z.ZodEnum<{
141
144
  native: "native";
@@ -289,6 +292,9 @@ export declare const machineSnapshotSchema: z.ZodObject<{
289
292
  unsupported: "unsupported";
290
293
  }>;
291
294
  }, z.core.$strip>>;
295
+ runtimeEvents: z.ZodOptional<z.ZodObject<{
296
+ forwardCursor: z.ZodBoolean;
297
+ }, z.core.$strip>>;
292
298
  sidechat: z.ZodOptional<z.ZodObject<{
293
299
  strategy: z.ZodEnum<{
294
300
  native: "native";
@@ -72,6 +72,9 @@ export const capabilitiesSchema = z.object({
72
72
  daemonVersion: z.string().optional(),
73
73
  sessionPrimitives: sessionPrimitivesSchema.optional(),
74
74
  turnPrimitives: turnPrimitivesSchema.optional(),
75
+ runtimeEvents: z.object({
76
+ forwardCursor: z.boolean(),
77
+ }).optional(),
75
78
  sidechat: sidechatCapabilitySchema.optional(),
76
79
  directoryBrowser: z.boolean().optional(),
77
80
  filePreview: z.boolean().optional(),
@@ -66,6 +66,9 @@ export type MachineSnapshot = {
66
66
  turnPrimitives?: {
67
67
  steer: "native" | "unsupported";
68
68
  };
69
+ runtimeEvents?: {
70
+ forwardCursor: boolean;
71
+ };
69
72
  sidechat?: SidechatCapability;
70
73
  directoryBrowser?: boolean;
71
74
  filePreview?: boolean;