@cydm/happy-elves 0.1.0-beta.274 → 0.1.0-beta.277

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.
@@ -315,7 +315,7 @@ function foldAliasedRuntimeEvents(events) {
315
315
  const profiles = new Map();
316
316
  const runtimeToRequest = new Map();
317
317
  for (const event of events) {
318
- const profile = profiles.get(event.turnId) ?? { output: "" };
318
+ const profile = profiles.get(event.turnId) ?? { output: "", status: "running" };
319
319
  const payload = event.decoded;
320
320
  if (payload?.type === "user_prompt" && profile.prompt === undefined) {
321
321
  profile.prompt = payload.text.trim();
@@ -328,6 +328,7 @@ function foldAliasedRuntimeEvents(events) {
328
328
  if (runtimeTurnId && runtimeTurnId !== event.turnId && !runtimeTurnId.startsWith("event:")) {
329
329
  runtimeToRequest.set(runtimeTurnId, event.turnId);
330
330
  }
331
+ profile.status = payload.status;
331
332
  }
332
333
  profiles.set(event.turnId, profile);
333
334
  }
@@ -345,11 +346,46 @@ function foldAliasedRuntimeEvents(events) {
345
346
  duplicateRuntimeToRequest.set(runtimeTurnId, requestTurnId);
346
347
  }
347
348
  }
349
+ const inferredRuntimeToRequest = inferRuntimeCompletionAliases(profiles);
350
+ const inferredRequestTurns = new Set(inferredRuntimeToRequest.values());
351
+ const foldedRuntimeToRequest = new Map([...duplicateRuntimeToRequest, ...inferredRuntimeToRequest]);
352
+ const foldedEvents = [];
353
+ for (const event of events) {
354
+ if (duplicateRuntimeTurns.has(event.turnId))
355
+ continue;
356
+ const inferredRequestTurnId = inferredRuntimeToRequest.get(event.turnId);
357
+ if (inferredRequestTurnId) {
358
+ if (event.decoded?.type === "user_prompt" && profiles.get(inferredRequestTurnId)?.prompt)
359
+ continue;
360
+ foldedEvents.push({ ...event, turnId: inferredRequestTurnId });
361
+ continue;
362
+ }
363
+ if (inferredRequestTurns.has(event.turnId) &&
364
+ event.decoded?.type === "text_delta" &&
365
+ event.decoded.stream !== "thought") {
366
+ continue;
367
+ }
368
+ foldedEvents.push(event);
369
+ }
348
370
  return {
349
- events: duplicateRuntimeTurns.size === 0 ? events : events.filter((event) => !duplicateRuntimeTurns.has(event.turnId)),
350
- runtimeToRequest: duplicateRuntimeToRequest,
371
+ events: foldedEvents,
372
+ runtimeToRequest: foldedRuntimeToRequest,
351
373
  };
352
374
  }
375
+ function inferRuntimeCompletionAliases(profiles) {
376
+ const requestTurns = [...profiles.entries()].filter(([turnId, profile]) => turnId.startsWith("turn_") && profile.status === "running" && Boolean(profile.prompt?.trim()));
377
+ const runtimeTurns = [...profiles.entries()].filter(([turnId, profile]) => !turnId.startsWith("turn_") && profile.status !== "running" && Boolean(profile.prompt?.trim()));
378
+ const result = new Map();
379
+ for (const [runtimeTurnId, runtime] of runtimeTurns) {
380
+ const candidates = requestTurns.filter(([, request]) => promptCompatible(request.prompt ?? "", runtime.prompt ?? "") &&
381
+ outputCompatibleForInferredAlias(request.output, runtime.output, request.prompt ?? "", runtime.prompt ?? ""));
382
+ if (candidates.length !== 1)
383
+ continue;
384
+ const [requestTurnId] = candidates[0];
385
+ result.set(runtimeTurnId, requestTurnId);
386
+ }
387
+ return result;
388
+ }
353
389
  function dedupeSyntheticHistoricalEvents(events) {
354
390
  const groups = new Map();
355
391
  for (const event of events) {
@@ -408,6 +444,23 @@ function isDuplicateRuntimeOutput(requestOutput, runtimeOutput) {
408
444
  const runtime = comparableOutput(runtimeOutput);
409
445
  return !runtime || request === runtime || request.includes(runtime);
410
446
  }
447
+ function promptCompatible(requestPrompt, runtimePrompt) {
448
+ const request = comparableOutput(requestPrompt);
449
+ const runtime = comparableOutput(runtimePrompt);
450
+ if (!request || !runtime)
451
+ return false;
452
+ if (request === runtime)
453
+ return true;
454
+ return (request.length >= 40 && runtime.includes(request)) ||
455
+ (runtime.length >= 40 && request.includes(runtime));
456
+ }
457
+ function outputCompatibleForInferredAlias(requestOutput, runtimeOutput, requestPrompt, runtimePrompt) {
458
+ const request = comparableOutput(requestOutput);
459
+ const runtime = comparableOutput(runtimeOutput);
460
+ if (request && runtime)
461
+ return request.includes(runtime) || runtime.includes(request);
462
+ return comparableOutput(requestPrompt) === comparableOutput(runtimePrompt) && Boolean(request || runtime);
463
+ }
411
464
  function comparableOutput(value) {
412
465
  return value.replace(/\s+/g, " ").trim();
413
466
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves-cli",
3
- "version": "0.1.0-beta.274",
3
+ "version": "0.1.0-beta.277",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -248,7 +248,7 @@ async function runStage(stage) {
248
248
  await log("$ " + stage.command + " " + args.map(shellQuote).join(" "));
249
249
  const result = await run(stage.command, args, {
250
250
  cwd: input.repoCwd,
251
- env: controllerEnv(),
251
+ env: envForStage(stage),
252
252
  });
253
253
  await log(result.stdout);
254
254
  if (result.stderr) await log(result.stderr);
@@ -300,16 +300,20 @@ async function run(command, args, options) {
300
300
  });
301
301
  }
302
302
 
303
- function controllerEnv() {
303
+ function envForStage(stage) {
304
+ const env = { ...process.env };
305
+ if (stage.phase !== "update" && stage.phase !== "notify") {
306
+ delete env.HAPPY_ELVES_RELAY_URL;
307
+ delete env.HAPPY_ELVES_CONTROLLER_TOKEN;
308
+ delete env.HAPPY_ELVES_ACCOUNT_SECRET;
309
+ return env;
310
+ }
304
311
  const config = input.controllerConfig;
305
- return {
306
- ...process.env,
307
- ...(config ? {
308
- HAPPY_ELVES_RELAY_URL: config.relayUrl,
309
- HAPPY_ELVES_CONTROLLER_TOKEN: config.controllerToken,
310
- HAPPY_ELVES_ACCOUNT_SECRET: config.accountSecret,
311
- } : {}),
312
- };
312
+ if (!config) return env;
313
+ env.HAPPY_ELVES_RELAY_URL = config.relayUrl;
314
+ env.HAPPY_ELVES_CONTROLLER_TOKEN = config.controllerToken;
315
+ env.HAPPY_ELVES_ACCOUNT_SECRET = config.accountSecret;
316
+ return env;
313
317
  }
314
318
 
315
319
  async function setStage(name, patch) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves-daemon",
3
- "version": "0.1.0-beta.274",
3
+ "version": "0.1.0-beta.277",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -396,13 +396,30 @@ export function handleMachineMessage(context, connection, message) {
396
396
  return;
397
397
  }
398
398
  const ts = now();
399
+ const promptTurnForRequest = message.requestId
400
+ ? promptTurnForRequestId(db, connection.accountId, message.sessionId, connection.machineId, message.requestId)
401
+ : undefined;
402
+ const activeClaim = activePromptClaim(db, connection.accountId, message.sessionId, connection.machineId);
403
+ const replacementRequestTurn = promptTurnForRequest?.turn_id ??
404
+ (sessionRow.status === "running" && message.status ? activeClaim?.turn_id : undefined);
405
+ const replacementRequestId = promptTurnForRequest?.request_id ??
406
+ (replacementRequestTurn === activeClaim?.turn_id ? activeClaim?.request_id : undefined);
399
407
  const inserted = [];
400
408
  db.exec("BEGIN");
401
409
  try {
402
410
  db.prepare(`DELETE FROM session_events
403
411
  WHERE account_id = ? AND session_id = ? AND machine_id = ?`).run(connection.accountId, message.sessionId, connection.machineId);
404
- db.prepare(`DELETE FROM session_turn_aliases
405
- WHERE account_id = ? AND session_id = ? AND machine_id = ?`).run(connection.accountId, message.sessionId, connection.machineId);
412
+ if (replacementRequestTurn) {
413
+ recordAliasesForRuntimeTurnIds({
414
+ accountId: connection.accountId,
415
+ sessionId: message.sessionId,
416
+ machineId: connection.machineId,
417
+ requestTurnId: replacementRequestTurn,
418
+ requestId: replacementRequestId,
419
+ runtimeTurnIds: [message.lastTurnId, message.currentHead, replacementBasis?.turnId],
420
+ recordTurnAlias,
421
+ });
422
+ }
406
423
  clearPendingSessionHeadAdvances(db, connection.accountId, message.sessionId, connection.machineId);
407
424
  for (const event of message.events) {
408
425
  const result = db
@@ -449,6 +466,16 @@ export function handleMachineMessage(context, connection, message) {
449
466
  encrypted_metadata = COALESCE(?, encrypted_metadata),
450
467
  updated_at = ?
451
468
  WHERE id = ? AND account_id = ? AND machine_id = ?`).run(message.status ?? null, message.currentHead ?? null, lastEvent ? `event:${lastEvent.id}` : sessionRow.current_head, message.lastTurnId ?? null, lastEvent?.turnId ?? sessionRow.last_turn_id, headBasisEvent?.id ?? null, headBasisEvent?.messageId ?? null, headBasisOrder?.time ?? null, headBasisOrder?.seq ?? null, message.encryptedMetadata ? JSON.stringify(message.encryptedMetadata) : null, ts, message.sessionId, connection.accountId, connection.machineId);
469
+ if (message.status && replacementRequestTurn) {
470
+ finishSessionClaim({
471
+ accountId: connection.accountId,
472
+ sessionId: message.sessionId,
473
+ ...(replacementRequestId ? { requestId: replacementRequestId } : {}),
474
+ turnId: replacementRequestTurn,
475
+ terminalStatus: message.status,
476
+ finishedAt: ts,
477
+ });
478
+ }
452
479
  db.exec("COMMIT");
453
480
  }
454
481
  catch (error) {
@@ -464,7 +491,7 @@ export function handleMachineMessage(context, connection, message) {
464
491
  broadcastControllers(connection.accountId, {
465
492
  type: "server:sessionTranscriptReplaced",
466
493
  session,
467
- events: inserted,
494
+ events: inserted.map((event) => canonicalEventForControllers(db, connection.accountId, event)),
468
495
  hasMoreBefore: message.hasMoreBefore,
469
496
  nextBefore: message.nextBefore,
470
497
  });
@@ -540,6 +567,23 @@ export function handleMachineMessage(context, connection, message) {
540
567
  }
541
568
  if (message.type === "machine:sessionHeadAdvanced") {
542
569
  const ts = now();
570
+ const sessionBefore = db
571
+ .prepare("SELECT * FROM sessions WHERE id = ? AND account_id = ? AND machine_id = ?")
572
+ .get(message.sessionId, connection.accountId, connection.machineId);
573
+ const activeClaim = sessionBefore
574
+ ? activePromptClaim(db, connection.accountId, message.sessionId, connection.machineId)
575
+ : undefined;
576
+ if (sessionBefore?.status === "running" && activeClaim && message.status) {
577
+ recordAliasesForRuntimeTurnIds({
578
+ accountId: connection.accountId,
579
+ sessionId: message.sessionId,
580
+ machineId: connection.machineId,
581
+ requestTurnId: activeClaim.turn_id,
582
+ requestId: activeClaim.request_id,
583
+ runtimeTurnIds: [message.lastTurnId, message.currentHead, message.basis.turnId],
584
+ recordTurnAlias,
585
+ });
586
+ }
543
587
  const input = {
544
588
  accountId: connection.accountId,
545
589
  sessionId: message.sessionId,
@@ -580,7 +624,22 @@ export function handleMachineMessage(context, connection, message) {
580
624
  acknowledgeMachineMessage();
581
625
  return;
582
626
  }
583
- const row = result.session ?? db.prepare("SELECT * FROM sessions WHERE id = ?").get(message.sessionId);
627
+ if (message.status && activeClaim && sessionBefore?.status === "running") {
628
+ db.prepare(`UPDATE sessions
629
+ SET status = ?,
630
+ last_turn_id = COALESCE(?, last_turn_id),
631
+ updated_at = ?
632
+ WHERE id = ? AND account_id = ? AND machine_id = ? AND status = 'running'`).run(message.status, message.lastTurnId ?? message.currentHead ?? message.basis.turnId, ts, message.sessionId, connection.accountId, connection.machineId);
633
+ finishSessionClaim({
634
+ accountId: connection.accountId,
635
+ sessionId: message.sessionId,
636
+ requestId: activeClaim.request_id,
637
+ turnId: activeClaim.turn_id,
638
+ terminalStatus: message.status,
639
+ finishedAt: ts,
640
+ });
641
+ }
642
+ const row = db.prepare("SELECT * FROM sessions WHERE id = ?").get(message.sessionId);
584
643
  if (row)
585
644
  broadcastControllers(connection.accountId, { type: "server:session", session: sessionSnapshot(row) });
586
645
  acknowledgeMachineMessage();
@@ -592,6 +651,17 @@ export function handleMachineMessage(context, connection, message) {
592
651
  .get(message.sessionId, connection.accountId, connection.machineId);
593
652
  if (!sessionRow)
594
653
  return;
654
+ maybeAliasHistoricalRuntimeEventToActiveClaim({
655
+ db,
656
+ accountId: connection.accountId,
657
+ sessionId: message.sessionId,
658
+ machineId: connection.machineId,
659
+ session: sessionRow,
660
+ turnId: message.turnId,
661
+ messageId: message.messageId,
662
+ occurredAt: message.occurredAt,
663
+ recordTurnAlias,
664
+ });
595
665
  const event = insertEvent({
596
666
  accountId: connection.accountId,
597
667
  sessionId: message.sessionId,
@@ -604,7 +674,7 @@ export function handleMachineMessage(context, connection, message) {
604
674
  payload: message.payload,
605
675
  });
606
676
  const pendingSessionRow = applyPendingSessionHeadAdvances(db, connection.accountId, message.sessionId, connection.machineId, now());
607
- broadcastControllers(connection.accountId, { type: "server:event", event });
677
+ broadcastControllers(connection.accountId, { type: "server:event", event: canonicalEventForControllers(db, connection.accountId, event) });
608
678
  if (pendingSessionRow)
609
679
  broadcastControllers(connection.accountId, { type: "server:session", session: sessionSnapshot(pendingSessionRow) });
610
680
  acknowledgeMachineMessage();
@@ -903,4 +973,82 @@ function latestEventForTurn(db, accountId, sessionId, machineId, turnId) {
903
973
  LIMIT 1`)
904
974
  .get(accountId, sessionId, machineId, turnId);
905
975
  }
976
+ function activePromptClaim(db, accountId, sessionId, machineId) {
977
+ return db
978
+ .prepare(`SELECT * FROM relay_session_claims
979
+ WHERE account_id = ? AND session_id = ? AND machine_id = ? AND state = 'active'
980
+ LIMIT 1`)
981
+ .get(accountId, sessionId, machineId);
982
+ }
983
+ function promptTurnForRequestId(db, accountId, sessionId, machineId, requestId) {
984
+ const claim = db
985
+ .prepare(`SELECT request_id, turn_id FROM relay_session_claims
986
+ WHERE account_id = ? AND session_id = ? AND machine_id = ? AND request_id = ? AND state = 'active'
987
+ LIMIT 1`)
988
+ .get(accountId, sessionId, machineId, requestId);
989
+ if (claim)
990
+ return claim;
991
+ return db
992
+ .prepare(`SELECT request_id, turn_id FROM relay_admissions
993
+ WHERE account_id = ? AND session_id = ? AND machine_id = ? AND request_id = ? AND kind = 'prompt' AND turn_id IS NOT NULL
994
+ LIMIT 1`)
995
+ .get(accountId, sessionId, machineId, requestId);
996
+ }
997
+ function maybeAliasHistoricalRuntimeEventToActiveClaim(input) {
998
+ if (input.session.status !== "running")
999
+ return;
1000
+ if (!isHistoricalRuntimeBackfillMessage(input.messageId, input.occurredAt))
1001
+ return;
1002
+ const claim = activePromptClaim(input.db, input.accountId, input.sessionId, input.machineId);
1003
+ if (!claim)
1004
+ return;
1005
+ recordAliasesForRuntimeTurnIds({
1006
+ accountId: input.accountId,
1007
+ sessionId: input.sessionId,
1008
+ machineId: input.machineId,
1009
+ requestTurnId: claim.turn_id,
1010
+ requestId: claim.request_id,
1011
+ runtimeTurnIds: [input.turnId],
1012
+ recordTurnAlias: input.recordTurnAlias,
1013
+ });
1014
+ }
1015
+ function isHistoricalRuntimeBackfillMessage(messageId, occurredAt) {
1016
+ if (messageId?.startsWith("hist_") || messageId?.startsWith("tail_"))
1017
+ return true;
1018
+ return occurredAt !== undefined;
1019
+ }
1020
+ function recordAliasesForRuntimeTurnIds(input) {
1021
+ for (const runtimeTurnId of input.runtimeTurnIds) {
1022
+ if (!isRuntimeTurnAliasCandidate(input.requestTurnId, runtimeTurnId))
1023
+ continue;
1024
+ input.recordTurnAlias({
1025
+ accountId: input.accountId,
1026
+ sessionId: input.sessionId,
1027
+ machineId: input.machineId,
1028
+ requestTurnId: input.requestTurnId,
1029
+ runtimeTurnId,
1030
+ requestId: input.requestId,
1031
+ });
1032
+ }
1033
+ }
1034
+ function isRuntimeTurnAliasCandidate(requestTurnId, runtimeTurnId) {
1035
+ if (!runtimeTurnId)
1036
+ return false;
1037
+ const value = runtimeTurnId.trim();
1038
+ if (!value || value === requestTurnId)
1039
+ return false;
1040
+ if (value.startsWith("event:"))
1041
+ return false;
1042
+ return true;
1043
+ }
1044
+ function canonicalEventForControllers(db, accountId, event) {
1045
+ const alias = db
1046
+ .prepare(`SELECT request_turn_id FROM session_turn_aliases
1047
+ WHERE account_id = ? AND session_id = ? AND machine_id = ? AND runtime_turn_id = ?
1048
+ LIMIT 1`)
1049
+ .get(accountId, event.sessionId, event.machineId, event.turnId);
1050
+ return alias && alias.request_turn_id !== event.turnId
1051
+ ? { ...event, turnId: alias.request_turn_id }
1052
+ : event;
1053
+ }
906
1054
  //# sourceMappingURL=machine-handlers.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves",
3
- "version": "0.1.0-beta.274",
3
+ "version": "0.1.0-beta.277",
4
4
  "description": "Remote controller for local coding agents with hosted or self-hosted relay support.",
5
5
  "type": "module",
6
6
  "bin": {