@cydm/happy-elves 0.1.0-beta.289 → 0.1.0-beta.294

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.289",
3
+ "version": "0.1.0-beta.294",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -117,12 +117,13 @@ function releaseStages(release) {
117
117
  { name: "p0-release", command: "pnpm", args: ["verify:p0:release"], phase: "gate" },
118
118
  { name: "deploy-production", command: "pnpm", args: ["deploy:production"], phase: "deploy" },
119
119
  { name: "verify-deploy", command: "pnpm", args: ["verify:deploy"], phase: "verify" },
120
+ { name: "publish-package", command: "pnpm", args: ["publish:public"], phase: "publish" },
120
121
  ];
121
122
  for (const machineId of release.targetMachineIds ?? []) {
122
123
  stages.push({
123
124
  name: `daemon-update:${machineId}`,
124
125
  command: "happy-elves",
125
- args: ["remote", "daemon-update", machineId, "--version", "latest", "--wait", "--json"],
126
+ args: ["remote", "daemon-update", machineId, "--version", "__PUBLIC_PACKAGE_VERSION__", "--wait", "--json"],
126
127
  phase: "update",
127
128
  });
128
129
  }
@@ -220,7 +221,7 @@ import fs from "node:fs/promises";
220
221
  import path from "node:path";
221
222
 
222
223
  const input = ${inputJson};
223
- const terminalStatuses = new Set(["gate_failed", "deploy_failed", "verify_failed", "update_failed", "notify_failed", "dogfood_failed", "succeeded", "failed"]);
224
+ const terminalStatuses = new Set(["gate_failed", "deploy_failed", "verify_failed", "publish_failed", "update_failed", "notify_failed", "dogfood_failed", "succeeded", "failed"]);
224
225
 
225
226
  main().catch(async (error) => {
226
227
  await fail("failed", error);
@@ -230,6 +231,7 @@ main().catch(async (error) => {
230
231
  async function main() {
231
232
  await fs.mkdir(path.dirname(input.logPath), { recursive: true });
232
233
  await updateState({ status: "running", updatedAt: new Date().toISOString() });
234
+ input.publicPackageVersion = await publicPackageVersion();
233
235
  await log("release runner started " + JSON.stringify({ jobId: input.jobId, profile: input.release.profile.name, repoCwd: input.repoCwd }));
234
236
  for (const stage of input.stages) {
235
237
  await runStage(stage);
@@ -243,6 +245,7 @@ async function runStage(stage) {
243
245
  const args = stage.args.map((arg) => {
244
246
  if (arg === "__RELEASE_SUMMARY__") return releaseSummary("Release completed");
245
247
  if (arg === "__RELEASE_REVIEW_SUMMARY__") return releaseSummary("Release completed; please review the evidence");
248
+ if (arg === "__PUBLIC_PACKAGE_VERSION__") return input.publicPackageVersion;
246
249
  return arg;
247
250
  });
248
251
  await log("$ " + stage.command + " " + args.map(shellQuote).join(" "));
@@ -271,11 +274,19 @@ function statusForPhase(phase) {
271
274
  if (phase === "gate") return "gate_failed";
272
275
  if (phase === "deploy") return "deploy_failed";
273
276
  if (phase === "verify") return "verify_failed";
277
+ if (phase === "publish") return "publish_failed";
274
278
  if (phase === "update") return "update_failed";
275
279
  if (phase === "notify") return "notify_failed";
276
280
  return "failed";
277
281
  }
278
282
 
283
+ async function publicPackageVersion() {
284
+ if (input.release.publicPackageVersion) return input.release.publicPackageVersion;
285
+ const result = await run("git", ["rev-list", "--count", "HEAD"], { cwd: input.repoCwd, env: process.env });
286
+ if (result.exitCode !== 0) throw new Error("failed to derive public package version: " + (result.stderr || result.stdout));
287
+ return "0.1.0-beta." + result.stdout.trim();
288
+ }
289
+
279
290
  async function fail(status, error) {
280
291
  const message = error instanceof Error ? error.stack || error.message : String(error);
281
292
  await log("release runner failed: " + message).catch(() => {});
@@ -306,6 +317,10 @@ function envForStage(stage) {
306
317
  delete env.HAPPY_ELVES_RELAY_URL;
307
318
  delete env.HAPPY_ELVES_CONTROLLER_TOKEN;
308
319
  delete env.HAPPY_ELVES_ACCOUNT_SECRET;
320
+ if (stage.phase !== "publish") {
321
+ delete env.NPM_TOKEN;
322
+ delete env.NODE_AUTH_TOKEN;
323
+ }
309
324
  return env;
310
325
  }
311
326
  const config = input.controllerConfig;
@@ -263,6 +263,7 @@ export async function handleFork(ws, config, command) {
263
263
  sourceSessionId: command.sessionId,
264
264
  sourceCheckpointId,
265
265
  }),
266
+ controllerEncryptedMetadata: command.controllerEncryptedMetadata,
266
267
  });
267
268
  if (authoritativeForkTranscript) {
268
269
  try {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves-daemon",
3
- "version": "0.1.0-beta.289",
3
+ "version": "0.1.0-beta.294",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -112,8 +112,8 @@ export function handleControllerMessage(context, connection, message) {
112
112
  db.exec("BEGIN");
113
113
  try {
114
114
  db.prepare(`INSERT INTO sessions
115
- (id, account_id, machine_id, agent, cwd, name, status, encrypted_metadata, created_at, updated_at)
116
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(sessionId, connection.accountId, message.machineId, message.agent, message.cwd, message.name, "new", message.encryptedMetadata ? JSON.stringify(message.encryptedMetadata) : null, createdAt, createdAt);
115
+ (id, account_id, machine_id, agent, cwd, name, status, encrypted_metadata, controller_encrypted_metadata, created_at, updated_at)
116
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(sessionId, connection.accountId, message.machineId, message.agent, message.cwd, message.name, "new", message.encryptedMetadata ? JSON.stringify(message.encryptedMetadata) : null, message.controllerEncryptedMetadata ? JSON.stringify(message.controllerEncryptedMetadata) : null, createdAt, createdAt);
117
117
  db.prepare(`INSERT INTO relay_admissions
118
118
  (account_id, request_id, session_id, machine_id, kind, turn_id, admission, dispatch, created_at, updated_at)
119
119
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(connection.accountId, message.requestId, sessionId, message.machineId, "createSession", null, "accepted", "queued", createdAt, createdAt);
@@ -251,8 +251,10 @@ export function handleControllerMessage(context, connection, message) {
251
251
  return;
252
252
  const updatedAt = now();
253
253
  db.prepare(`UPDATE sessions
254
- SET encrypted_metadata = ?, updated_at = ?
255
- WHERE id = ? AND account_id = ?`).run(JSON.stringify(message.encryptedMetadata), updatedAt, sessionRow.id, connection.accountId);
254
+ SET encrypted_metadata = ?,
255
+ controller_encrypted_metadata = COALESCE(?, controller_encrypted_metadata),
256
+ updated_at = ?
257
+ WHERE id = ? AND account_id = ?`).run(JSON.stringify(message.encryptedMetadata), message.controllerEncryptedMetadata ? JSON.stringify(message.controllerEncryptedMetadata) : null, updatedAt, sessionRow.id, connection.accountId);
256
258
  const updated = sessionSnapshot(db.prepare("SELECT * FROM sessions WHERE id = ? AND account_id = ?").get(sessionRow.id, connection.accountId));
257
259
  broadcastControllers(connection.accountId, { type: "server:session", session: updated });
258
260
  sendAck(connection, {
@@ -677,8 +679,8 @@ export function handleControllerMessage(context, connection, message) {
677
679
  const sessionId = randomId("ses");
678
680
  const createdAt = now();
679
681
  db.prepare(`INSERT INTO sessions
680
- (id, account_id, machine_id, agent, cwd, name, status, encrypted_metadata, created_at, updated_at)
681
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(sessionId, connection.accountId, message.machineId, message.agent, message.cwd, message.name, "new", message.encryptedMetadata ? JSON.stringify(message.encryptedMetadata) : null, createdAt, createdAt);
682
+ (id, account_id, machine_id, agent, cwd, name, status, encrypted_metadata, controller_encrypted_metadata, created_at, updated_at)
683
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(sessionId, connection.accountId, message.machineId, message.agent, message.cwd, message.name, "new", message.encryptedMetadata ? JSON.stringify(message.encryptedMetadata) : null, message.controllerEncryptedMetadata ? JSON.stringify(message.controllerEncryptedMetadata) : null, createdAt, createdAt);
682
684
  const session = sessionSnapshot(db.prepare("SELECT * FROM sessions WHERE id = ?").get(sessionId));
683
685
  broadcastControllers(connection.accountId, { type: "server:session", session });
684
686
  markCommandPending(connection.accountId, message.machineId, message.requestId);
@@ -749,6 +751,7 @@ export function handleControllerMessage(context, connection, message) {
749
751
  name: message.name,
750
752
  sourceEncryptedMetadata: sessionRow.encrypted_metadata ? parseJson(sessionRow.encrypted_metadata, undefined) : undefined,
751
753
  encryptedMetadata: message.encryptedMetadata,
754
+ controllerEncryptedMetadata: message.controllerEncryptedMetadata,
752
755
  });
753
756
  return;
754
757
  }
@@ -61,6 +61,7 @@ export function initDb(db) {
61
61
  name TEXT NOT NULL,
62
62
  status TEXT NOT NULL,
63
63
  encrypted_metadata TEXT,
64
+ controller_encrypted_metadata TEXT,
64
65
  current_head TEXT,
65
66
  last_turn_id TEXT,
66
67
  head_basis_event_id INTEGER,
@@ -291,6 +292,7 @@ export function initDb(db) {
291
292
  ensureColumn(db, "relay_admissions", "started_at", "INTEGER");
292
293
  ensureColumn(db, "relay_admissions", "finished_at", "INTEGER");
293
294
  ensureColumn(db, "relay_admissions", "terminal_status", "TEXT");
295
+ ensureColumn(db, "sessions", "controller_encrypted_metadata", "TEXT");
294
296
  ensureColumn(db, "relay_admissions", "last_error_code", "TEXT");
295
297
  ensureColumn(db, "relay_dispatch_outbox", "message", "TEXT");
296
298
  ensureColumn(db, "relay_dispatch_outbox", "command_type", "TEXT");
@@ -77,6 +77,7 @@ function sessionEventLogicalSeqValue(row) {
77
77
  }
78
78
  function canonicalEventRows(context, accountId, rows) {
79
79
  const cache = new Map();
80
+ const suppressCache = new Map();
80
81
  const seenHistorical = new Set();
81
82
  const canonicalRows = [];
82
83
  for (const row of rows) {
@@ -90,6 +91,20 @@ function canonicalEventRows(context, accountId, rows) {
90
91
  requestTurnId = alias?.request_turn_id ?? null;
91
92
  cache.set(key, requestTurnId);
92
93
  }
94
+ if (requestTurnId) {
95
+ let suppress = suppressCache.get(key);
96
+ if (suppress === undefined) {
97
+ suppress = context.aliasedRuntimeEventHasRequestEvents({
98
+ accountId,
99
+ sessionId: row.session_id,
100
+ machineId: row.machine_id,
101
+ runtimeTurnId: row.turn_id,
102
+ });
103
+ suppressCache.set(key, suppress);
104
+ }
105
+ if (suppress)
106
+ continue;
107
+ }
93
108
  const canonicalRow = requestTurnId ? { ...row, turn_id: requestTurnId } : row;
94
109
  const historicalKey = canonicalHistoricalEventKey(canonicalRow);
95
110
  if (historicalKey) {
@@ -284,8 +299,8 @@ export function registerHttpRoutes(app, context, controllerInviteTtlMs) {
284
299
  context.db.exec("BEGIN");
285
300
  try {
286
301
  context.db.prepare(`INSERT INTO sessions
287
- (id, account_id, machine_id, agent, cwd, name, status, encrypted_metadata, created_at, updated_at)
288
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(sessionId, token.account_id, token.machine_id, body.agent, body.cwd, body.name, "new", body.encryptedMetadata ? JSON.stringify(body.encryptedMetadata) : null, createdAt, createdAt);
302
+ (id, account_id, machine_id, agent, cwd, name, status, encrypted_metadata, controller_encrypted_metadata, created_at, updated_at)
303
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(sessionId, token.account_id, token.machine_id, body.agent, body.cwd, body.name, "new", body.encryptedMetadata ? JSON.stringify(body.encryptedMetadata) : null, body.controllerEncryptedMetadata ? JSON.stringify(body.controllerEncryptedMetadata) : null, createdAt, createdAt);
289
304
  context.db.prepare(`INSERT INTO relay_admissions
290
305
  (account_id, request_id, session_id, machine_id, kind, turn_id, admission, dispatch, created_at, updated_at)
291
306
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(token.account_id, body.requestId, sessionId, token.machine_id, "createSession", null, "accepted", "queued", createdAt, createdAt);
@@ -148,6 +148,19 @@ export declare const machineCreateSessionAdmissionSchema: z.ZodObject<{
148
148
  ciphertext: z.ZodString;
149
149
  }, z.core.$strip>;
150
150
  }, z.core.$strip>>;
151
+ controllerEncryptedMetadata: z.ZodOptional<z.ZodObject<{
152
+ v: z.ZodLiteral<1>;
153
+ alg: z.ZodLiteral<"A256GCM">;
154
+ scope: z.ZodString;
155
+ key: z.ZodObject<{
156
+ nonce: z.ZodString;
157
+ ciphertext: z.ZodString;
158
+ }, z.core.$strip>;
159
+ data: z.ZodObject<{
160
+ nonce: z.ZodString;
161
+ ciphertext: z.ZodString;
162
+ }, z.core.$strip>;
163
+ }, z.core.$strip>>;
151
164
  }, z.core.$strip>;
152
165
  export declare const machinePromptAdmissionSchema: z.ZodObject<{
153
166
  requestId: z.ZodString;
@@ -124,6 +124,7 @@ export const machineCreateSessionAdmissionSchema = z.object({
124
124
  name: z.string().min(1),
125
125
  model: z.string().min(1).optional(),
126
126
  encryptedMetadata: encryptedEnvelopeSchema.optional(),
127
+ controllerEncryptedMetadata: encryptedEnvelopeSchema.optional(),
127
128
  });
128
129
  export const machinePromptAdmissionSchema = z.object({
129
130
  requestId: z.string().min(1),
@@ -346,9 +346,9 @@ export function handleMachineMessage(context, connection, message) {
346
346
  }
347
347
  else {
348
348
  db.prepare(`INSERT INTO sessions
349
- (id, account_id, machine_id, agent, cwd, name, status, encrypted_metadata,
349
+ (id, account_id, machine_id, agent, cwd, name, status, encrypted_metadata, controller_encrypted_metadata,
350
350
  current_head, last_turn_id, source_session_id, source_checkpoint_id, created_at, updated_at)
351
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(message.sessionId, connection.accountId, connection.machineId, source.agent, source.cwd, message.name ?? source.name, message.transcriptPending === true ? "running" : "ready", message.encryptedMetadata ? JSON.stringify(message.encryptedMetadata) : source.encrypted_metadata, message.sourceCheckpointId, source.last_turn_id, message.sourceSessionId, message.sourceCheckpointId, ts, ts);
351
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(message.sessionId, connection.accountId, connection.machineId, source.agent, source.cwd, message.name ?? source.name, message.transcriptPending === true ? "running" : "ready", message.encryptedMetadata ? JSON.stringify(message.encryptedMetadata) : source.encrypted_metadata, message.controllerEncryptedMetadata ? JSON.stringify(message.controllerEncryptedMetadata) : null, message.sourceCheckpointId, source.last_turn_id, message.sourceSessionId, message.sourceCheckpointId, ts, ts);
352
352
  }
353
353
  const row = db.prepare("SELECT * FROM sessions WHERE id = ?").get(message.sessionId);
354
354
  if (row)
@@ -532,7 +532,10 @@ export function handleMachineMessage(context, connection, message) {
532
532
  broadcastControllers(connection.accountId, {
533
533
  type: "server:sessionTranscriptReplaced",
534
534
  session,
535
- events: inserted.map((event) => canonicalEventForControllers(db, connection.accountId, event)),
535
+ events: inserted.flatMap((event) => {
536
+ const canonicalEvent = canonicalEventForControllers(db, connection.accountId, event);
537
+ return canonicalEvent ? [canonicalEvent] : [];
538
+ }),
536
539
  hasMoreBefore: message.hasMoreBefore,
537
540
  nextBefore: message.nextBefore,
538
541
  });
@@ -601,7 +604,10 @@ export function handleMachineMessage(context, connection, message) {
601
604
  broadcastControllers(connection.accountId, {
602
605
  type: "server:sessionTranscriptPrepended",
603
606
  session,
604
- events: inserted,
607
+ events: inserted.flatMap((event) => {
608
+ const canonicalEvent = canonicalEventForControllers(db, connection.accountId, event);
609
+ return canonicalEvent ? [canonicalEvent] : [];
610
+ }),
605
611
  hasMoreBefore: message.hasMoreBefore,
606
612
  nextBefore: message.nextBefore,
607
613
  });
@@ -619,20 +625,6 @@ export function handleMachineMessage(context, connection, message) {
619
625
  acknowledgeMachineMessage();
620
626
  return;
621
627
  }
622
- const activeClaim = sessionBefore
623
- ? activePromptClaim(db, connection.accountId, message.sessionId, connection.machineId)
624
- : undefined;
625
- if (sessionBefore?.status === "running" && activeClaim && message.status) {
626
- recordAliasesForRuntimeTurnIds({
627
- accountId: connection.accountId,
628
- sessionId: message.sessionId,
629
- machineId: connection.machineId,
630
- requestTurnId: activeClaim.turn_id,
631
- requestId: activeClaim.request_id,
632
- runtimeTurnIds: [message.lastTurnId, message.currentHead, message.basis.turnId],
633
- recordTurnAlias,
634
- });
635
- }
636
628
  const input = {
637
629
  accountId: connection.accountId,
638
630
  sessionId: message.sessionId,
@@ -673,21 +665,6 @@ export function handleMachineMessage(context, connection, message) {
673
665
  acknowledgeMachineMessage();
674
666
  return;
675
667
  }
676
- if (message.status && activeClaim && sessionBefore?.status === "running") {
677
- db.prepare(`UPDATE sessions
678
- SET status = ?,
679
- last_turn_id = COALESCE(?, last_turn_id),
680
- updated_at = ?
681
- 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);
682
- finishSessionClaim({
683
- accountId: connection.accountId,
684
- sessionId: message.sessionId,
685
- requestId: activeClaim.request_id,
686
- turnId: activeClaim.turn_id,
687
- terminalStatus: message.status,
688
- finishedAt: ts,
689
- });
690
- }
691
668
  const row = db.prepare("SELECT * FROM sessions WHERE id = ?").get(message.sessionId);
692
669
  if (row)
693
670
  broadcastControllers(connection.accountId, { type: "server:session", session: sessionSnapshot(row) });
@@ -704,17 +681,6 @@ export function handleMachineMessage(context, connection, message) {
704
681
  acknowledgeMachineMessage();
705
682
  return;
706
683
  }
707
- maybeAliasHistoricalRuntimeEventToActiveClaim({
708
- db,
709
- accountId: connection.accountId,
710
- sessionId: message.sessionId,
711
- machineId: connection.machineId,
712
- session: sessionRow,
713
- turnId: message.turnId,
714
- messageId: message.messageId,
715
- occurredAt: message.occurredAt,
716
- recordTurnAlias,
717
- });
718
684
  const event = insertEvent({
719
685
  accountId: connection.accountId,
720
686
  sessionId: message.sessionId,
@@ -727,7 +693,9 @@ export function handleMachineMessage(context, connection, message) {
727
693
  payload: message.payload,
728
694
  });
729
695
  const pendingSessionRow = applyPendingSessionHeadAdvances(db, connection.accountId, message.sessionId, connection.machineId, now());
730
- broadcastControllers(connection.accountId, { type: "server:event", event: canonicalEventForControllers(db, connection.accountId, event) });
696
+ const canonicalEvent = canonicalEventForControllers(db, connection.accountId, event);
697
+ if (canonicalEvent)
698
+ broadcastControllers(connection.accountId, { type: "server:event", event: canonicalEvent });
731
699
  if (pendingSessionRow)
732
700
  broadcastControllers(connection.accountId, { type: "server:session", session: sessionSnapshot(pendingSessionRow) });
733
701
  acknowledgeMachineMessage();
@@ -943,7 +911,15 @@ function applySessionHeadAdvance(db, input, ts) {
943
911
  sessionRow.current_head === input.currentHead ||
944
912
  sessionRow.last_turn_id === input.lastTurnId;
945
913
  const shouldAdvance = !rewriteFenced && (comparison > 0 || (comparison === 0 && currentHeadIsBasisEvent));
914
+ const activeClaim = activePromptClaim(db, input.accountId, input.sessionId, input.machineId);
915
+ const activeClaimOwnsHeadAdvance = !activeClaim ||
916
+ input.basis.turnId === activeClaim.turn_id ||
917
+ input.currentHead === activeClaim.turn_id ||
918
+ input.lastTurnId === activeClaim.turn_id;
919
+ if (!activeClaimOwnsHeadAdvance)
920
+ return { status: "noop", session: sessionRow };
946
921
  const shouldSettleStatus = !rewriteFenced &&
922
+ !activeClaim &&
947
923
  input.status !== undefined &&
948
924
  sessionRow.status === "running" &&
949
925
  comparison === 0 &&
@@ -960,7 +936,7 @@ function applySessionHeadAdvance(db, input, ts) {
960
936
  head_basis_turn_seq = COALESCE(?, head_basis_turn_seq),
961
937
  encrypted_metadata = COALESCE(?, encrypted_metadata),
962
938
  updated_at = ?
963
- WHERE id = ? AND account_id = ? AND machine_id = ?`).run(input.status && sessionRow.status === "running" ? input.status : null, shouldAdvance ? input.currentHead : null, shouldAdvance ? input.lastTurnId : null, shouldAdvance ? basisEvent.id : null, shouldAdvance ? basisEvent.message_id : null, shouldAdvance ? nextOrder.time : null, shouldAdvance ? nextOrder.seq : null, input.encryptedMetadata ? JSON.stringify(input.encryptedMetadata) : null, ts, input.sessionId, input.accountId, input.machineId);
939
+ WHERE id = ? AND account_id = ? AND machine_id = ?`).run(shouldSettleStatus ? input.status ?? null : null, shouldAdvance ? input.currentHead : null, shouldAdvance ? input.lastTurnId : null, shouldAdvance ? basisEvent.id : null, shouldAdvance ? basisEvent.message_id : null, shouldAdvance ? nextOrder.time : null, shouldAdvance ? nextOrder.seq : null, input.encryptedMetadata ? JSON.stringify(input.encryptedMetadata) : null, ts, input.sessionId, input.accountId, input.machineId);
964
940
  const session = db.prepare("SELECT * FROM sessions WHERE id = ?").get(input.sessionId);
965
941
  return { status: "applied", session };
966
942
  }
@@ -1061,29 +1037,6 @@ function promptTurnForRequestId(db, accountId, sessionId, machineId, requestId)
1061
1037
  LIMIT 1`)
1062
1038
  .get(accountId, sessionId, machineId, requestId);
1063
1039
  }
1064
- function maybeAliasHistoricalRuntimeEventToActiveClaim(input) {
1065
- if (input.session.status !== "running")
1066
- return;
1067
- if (!isHistoricalRuntimeBackfillMessage(input.messageId, input.occurredAt))
1068
- return;
1069
- const claim = activePromptClaim(input.db, input.accountId, input.sessionId, input.machineId);
1070
- if (!claim)
1071
- return;
1072
- recordAliasesForRuntimeTurnIds({
1073
- accountId: input.accountId,
1074
- sessionId: input.sessionId,
1075
- machineId: input.machineId,
1076
- requestTurnId: claim.turn_id,
1077
- requestId: claim.request_id,
1078
- runtimeTurnIds: [input.turnId],
1079
- recordTurnAlias: input.recordTurnAlias,
1080
- });
1081
- }
1082
- function isHistoricalRuntimeBackfillMessage(messageId, occurredAt) {
1083
- if (messageId?.startsWith("hist_") || messageId?.startsWith("tail_"))
1084
- return true;
1085
- return occurredAt !== undefined;
1086
- }
1087
1040
  function recordAliasesForRuntimeTurnIds(input) {
1088
1041
  for (const runtimeTurnId of input.runtimeTurnIds) {
1089
1042
  if (!isRuntimeTurnAliasCandidate(input.requestTurnId, runtimeTurnId))
@@ -1114,8 +1067,33 @@ function canonicalEventForControllers(db, accountId, event) {
1114
1067
  WHERE account_id = ? AND session_id = ? AND machine_id = ? AND runtime_turn_id = ?
1115
1068
  LIMIT 1`)
1116
1069
  .get(accountId, event.sessionId, event.machineId, event.turnId);
1117
- return alias && alias.request_turn_id !== event.turnId
1118
- ? { ...event, turnId: alias.request_turn_id }
1119
- : event;
1070
+ if (!alias || alias.request_turn_id === event.turnId)
1071
+ return event;
1072
+ if (aliasedRuntimeEventHasRequestEvents(db, {
1073
+ accountId,
1074
+ sessionId: event.sessionId,
1075
+ machineId: event.machineId,
1076
+ runtimeTurnId: event.turnId,
1077
+ })) {
1078
+ return undefined;
1079
+ }
1080
+ return { ...event, turnId: alias.request_turn_id };
1081
+ }
1082
+ function aliasedRuntimeEventHasRequestEvents(db, params) {
1083
+ const alias = db
1084
+ .prepare(`SELECT request_turn_id FROM session_turn_aliases
1085
+ WHERE account_id = ? AND session_id = ? AND machine_id = ? AND runtime_turn_id = ?
1086
+ LIMIT 1`)
1087
+ .get(params.accountId, params.sessionId, params.machineId, params.runtimeTurnId);
1088
+ if (!alias || alias.request_turn_id === params.runtimeTurnId)
1089
+ return false;
1090
+ const existing = db
1091
+ .prepare(`SELECT COUNT(*) AS count FROM (
1092
+ SELECT 1 FROM session_events
1093
+ WHERE account_id = ? AND session_id = ? AND machine_id = ? AND turn_id = ?
1094
+ LIMIT 3
1095
+ )`)
1096
+ .get(params.accountId, params.sessionId, params.machineId, alias.request_turn_id);
1097
+ return (existing?.count ?? 0) >= 3;
1120
1098
  }
1121
1099
  //# sourceMappingURL=machine-handlers.js.map
@@ -149,6 +149,7 @@ export function sessionSnapshot(row, capabilitiesForMachine, historyModelForMach
149
149
  createdAt: row.created_at,
150
150
  updatedAt: row.updated_at,
151
151
  encryptedMetadata: parseJson(row.encrypted_metadata, undefined),
152
+ controllerEncryptedMetadata: parseJson(row.controller_encrypted_metadata, undefined),
152
153
  currentHead: row.current_head ?? undefined,
153
154
  lastTurnId: row.last_turn_id ?? undefined,
154
155
  headBasis: sessionHeadBasis(row),
@@ -571,6 +571,9 @@ export function createRelayContext(options) {
571
571
  const currentHead = session?.current_head;
572
572
  if (session?.status === "closed")
573
573
  return false;
574
+ if (session?.status === "running" && params.source === "machine" && session.last_turn_id && params.turnId !== session.last_turn_id) {
575
+ return false;
576
+ }
574
577
  if (!currentHead)
575
578
  return true;
576
579
  if (typeof session?.head_basis_message_id === "string" &&
@@ -578,9 +581,6 @@ export function createRelayContext(options) {
578
581
  params.source === "machine") {
579
582
  return false;
580
583
  }
581
- if (session?.status === "running" && params.source === "machine" && session.last_turn_id && params.turnId !== session.last_turn_id) {
582
- return false;
583
- }
584
584
  const nextOrder = logicalEventOrder({
585
585
  id: eventId,
586
586
  createdAt,
@@ -40,6 +40,7 @@ export type SessionRow = {
40
40
  name: string;
41
41
  status: SessionSnapshot["status"];
42
42
  encrypted_metadata: string | null;
43
+ controller_encrypted_metadata: string | null;
43
44
  current_head: string | null;
44
45
  last_turn_id: string | null;
45
46
  head_basis_event_id: number | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves",
3
- "version": "0.1.0-beta.289",
3
+ "version": "0.1.0-beta.294",
4
4
  "description": "Remote controller for local coding agents with hosted or self-hosted relay support.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,4 +1,4 @@
1
- import { decryptJson, encryptJson, normalizeSessionTags, normalizeSessionName, sessionTagsMatch, parseServerMessage, randomId, } from "../../shared/dist/index.js";
1
+ import { decryptJson, encryptJson, normalizeSessionTags, sessionTagKeys, normalizeSessionName, sessionTagsMatch, parseServerMessage, randomId, } from "../../shared/dist/index.js";
2
2
  import { addControllerDevice, claimControllerInvite, createControllerInvite, createPairingCode, createScopedToken, deleteMemory as deleteMemoryRecord, listDevices, listEncryptedMemories, listMemorySettings, renameDevice, markSessionRecent, pinWorkspaceCwd, listScopedTokens, revokeDevice, revokeScopedToken, saveEncryptedMemory, setSessionStar, setMemorySettings, setWorkspaceProjectPreference, tokenSelf, unpinWorkspaceCwd, workspaceSyncState, } from "./account.js";
3
3
  import { ControllerClientError, ControllerCommandError } from "./errors.js";
4
4
  import { authenticatedJson, normalizeRelayUrl, readRelayJson, relayHttpError } from "./http.js";
@@ -30,6 +30,11 @@ function runtimeSessionIdsForMetadata(metadata) {
30
30
  function isCanonicalRuntimeSessionCandidate(left, right) {
31
31
  return left.createdAt < right.createdAt || (left.createdAt === right.createdAt && left.id < right.id);
32
32
  }
33
+ function controllerSessionMetadata(tags) {
34
+ if (tags.length === 0)
35
+ return undefined;
36
+ return { tags, tagKeys: sessionTagKeys(tags) };
37
+ }
33
38
  function runSettlementTimeoutMs(options) {
34
39
  if (options.waitTimeoutMs !== undefined)
35
40
  return options.waitTimeoutMs;
@@ -226,6 +231,7 @@ export class ControllerClient {
226
231
  const requestedRuntimeName = input.runtimeName === undefined ? undefined : normalizeSessionName(input.runtimeName);
227
232
  const model = normalizeOptionalString(input.model);
228
233
  const tags = normalizeSessionTags(input.tags);
234
+ const controllerMetadata = controllerSessionMetadata(tags);
229
235
  const requestId = randomId("req");
230
236
  const result = await this.sendCommand({
231
237
  type: "controller:createSession",
@@ -246,29 +252,34 @@ export class ControllerClient {
246
252
  ...(input.hiddenFromWorkspace ? { hiddenFromWorkspace: true, visibility: "hidden" } : {}),
247
253
  permissionMode: input.permissionMode,
248
254
  ...(input.source ? { source: input.source } : {}),
249
- ...(tags.length > 0 ? { tags, tagKeys: tags.map((tag) => tag.trim().replace(/\s+/gu, " ").toLocaleLowerCase()) } : {}),
255
+ ...(tags.length > 0 ? { tags, tagKeys: sessionTagKeys(tags) } : {}),
250
256
  }),
257
+ ...(controllerMetadata ? {
258
+ controllerEncryptedMetadata: await encryptJson(this.config.accountSecret, `session:controller-metadata:create:${requestId}`, controllerMetadata),
259
+ } : {}),
251
260
  });
252
261
  return { ...result, requestId };
253
262
  }
254
263
  async updateSessionMetadata(input) {
255
264
  const sessionId = requireNonEmpty(input.sessionId, "sessionId");
256
265
  const requestId = randomId("req");
257
- const metadata = { ...input.metadata };
258
- const tags = normalizeSessionTags(metadata.tags);
266
+ const tags = normalizeSessionTags(input.metadata.tags);
267
+ const runtimeMetadata = { ...input.metadata };
259
268
  if (tags.length > 0) {
260
- metadata.tags = tags;
261
- metadata.tagKeys = tags.map((tag) => tag.trim().replace(/\s+/gu, " ").toLocaleLowerCase());
269
+ runtimeMetadata.tags = tags;
270
+ runtimeMetadata.tagKeys = sessionTagKeys(tags);
262
271
  }
263
272
  else {
264
- delete metadata.tags;
265
- delete metadata.tagKeys;
273
+ delete runtimeMetadata.tags;
274
+ delete runtimeMetadata.tagKeys;
266
275
  }
276
+ const controllerMetadata = controllerSessionMetadata(tags) ?? {};
267
277
  const result = await this.sendCommand({
268
278
  type: "controller:updateSessionMetadata",
269
279
  requestId,
270
280
  sessionId,
271
- encryptedMetadata: await encryptJson(this.config.accountSecret, `session:${sessionId}:metadata:update:${requestId}`, metadata),
281
+ encryptedMetadata: await encryptJson(this.config.accountSecret, `session:${sessionId}:metadata:update:${requestId}`, runtimeMetadata),
282
+ controllerEncryptedMetadata: await encryptJson(this.config.accountSecret, `session:${sessionId}:controller-metadata:update:${requestId}`, controllerMetadata),
272
283
  });
273
284
  return { requestId, sessionId: result.sessionId ?? sessionId };
274
285
  }
@@ -573,6 +584,7 @@ export class ControllerClient {
573
584
  const from = requireNonEmpty(input.from, "from");
574
585
  const name = requireNonEmpty(input.name, "name");
575
586
  const tags = normalizeSessionTags(input.tags);
587
+ const controllerMetadata = controllerSessionMetadata(tags);
576
588
  const requestId = randomId("req");
577
589
  return await this.sendCommand({
578
590
  type: "controller:fork",
@@ -584,8 +596,11 @@ export class ControllerClient {
584
596
  sourceSessionId: checkedSessionId,
585
597
  from,
586
598
  name,
587
- ...(tags.length > 0 ? { tags, tagKeys: tags.map((tag) => tag.trim().replace(/\s+/gu, " ").toLocaleLowerCase()) } : {}),
599
+ ...(tags.length > 0 ? { tags, tagKeys: sessionTagKeys(tags) } : {}),
588
600
  }),
601
+ ...(controllerMetadata ? {
602
+ controllerEncryptedMetadata: await encryptJson(this.config.accountSecret, `session:fork:${requestId}:controller-metadata`, controllerMetadata),
603
+ } : {}),
589
604
  });
590
605
  }
591
606
  async renameSession(sessionId, name) {
@@ -700,14 +715,33 @@ export class ControllerClient {
700
715
  }
701
716
  }
702
717
  async decodeSessionMetadata(session) {
703
- if (!session.encryptedMetadata)
704
- return {};
718
+ let runtimeMetadata = {};
719
+ let controllerMetadata = {};
705
720
  try {
706
- return await decryptJson(this.config.accountSecret, session.encryptedMetadata);
721
+ if (session.encryptedMetadata)
722
+ runtimeMetadata = await decryptJson(this.config.accountSecret, session.encryptedMetadata);
707
723
  }
708
724
  catch {
709
- return {};
725
+ runtimeMetadata = {};
726
+ }
727
+ try {
728
+ if (session.controllerEncryptedMetadata)
729
+ controllerMetadata = await decryptJson(this.config.accountSecret, session.controllerEncryptedMetadata);
730
+ }
731
+ catch {
732
+ controllerMetadata = {};
733
+ }
734
+ const merged = { ...runtimeMetadata, ...controllerMetadata };
735
+ const tags = normalizeSessionTags(controllerMetadata.tags ?? runtimeMetadata.tags);
736
+ if (tags.length > 0) {
737
+ merged.tags = tags;
738
+ merged.tagKeys = sessionTagKeys(tags);
739
+ }
740
+ else {
741
+ delete merged.tags;
742
+ delete merged.tagKeys;
710
743
  }
744
+ return merged;
711
745
  }
712
746
  async decodeMemory(memory) {
713
747
  const payload = await decryptJson(this.config.accountSecret, memory.encryptedMemory);
@@ -233,6 +233,19 @@ export declare const sessionSnapshotSchema: z.ZodObject<{
233
233
  ciphertext: z.ZodString;
234
234
  }, z.core.$strip>;
235
235
  }, z.core.$strip>>;
236
+ controllerEncryptedMetadata: z.ZodOptional<z.ZodObject<{
237
+ v: z.ZodLiteral<1>;
238
+ alg: z.ZodLiteral<"A256GCM">;
239
+ scope: z.ZodString;
240
+ key: z.ZodObject<{
241
+ nonce: z.ZodString;
242
+ ciphertext: z.ZodString;
243
+ }, z.core.$strip>;
244
+ data: z.ZodObject<{
245
+ nonce: z.ZodString;
246
+ ciphertext: z.ZodString;
247
+ }, z.core.$strip>;
248
+ }, z.core.$strip>>;
236
249
  currentHead: z.ZodOptional<z.ZodString>;
237
250
  lastTurnId: z.ZodOptional<z.ZodString>;
238
251
  headBasis: z.ZodOptional<z.ZodObject<{
@@ -563,6 +576,7 @@ export declare const releaseJobResultSchema: z.ZodObject<{
563
576
  gate_failed: "gate_failed";
564
577
  deploy_failed: "deploy_failed";
565
578
  verify_failed: "verify_failed";
579
+ publish_failed: "publish_failed";
566
580
  update_failed: "update_failed";
567
581
  notify_failed: "notify_failed";
568
582
  dogfood_failed: "dogfood_failed";
@@ -83,6 +83,7 @@ export const sessionSnapshotSchema = z.object({
83
83
  createdAt: z.number(),
84
84
  updatedAt: z.number(),
85
85
  encryptedMetadata: encryptedEnvelopeSchema.optional(),
86
+ controllerEncryptedMetadata: encryptedEnvelopeSchema.optional(),
86
87
  currentHead: z.string().min(1).optional(),
87
88
  lastTurnId: z.string().min(1).optional(),
88
89
  headBasis: z.object({
@@ -260,7 +261,7 @@ export const releaseJobResultSchema = z.object({
260
261
  action: z.enum(["start", "status", "logs"]),
261
262
  accepted: z.boolean().optional(),
262
263
  jobId: z.string().min(1).optional(),
263
- status: z.enum(["accepted", "running", "gate_failed", "deploy_failed", "verify_failed", "update_failed", "notify_failed", "dogfood_failed", "succeeded", "failed"]).optional(),
264
+ status: z.enum(["accepted", "running", "gate_failed", "deploy_failed", "verify_failed", "publish_failed", "update_failed", "notify_failed", "dogfood_failed", "succeeded", "failed"]).optional(),
264
265
  profileName: z.string().min(1).optional(),
265
266
  releaseSessionId: z.string().min(1).optional(),
266
267
  machineId: z.string().min(1).optional(),
@@ -61,6 +61,7 @@ export type SessionSnapshot = {
61
61
  createdAt: number;
62
62
  updatedAt: number;
63
63
  encryptedMetadata?: EncryptedEnvelope;
64
+ controllerEncryptedMetadata?: EncryptedEnvelope;
64
65
  currentHead?: string;
65
66
  lastTurnId?: string;
66
67
  headBasis?: {
@@ -209,7 +210,7 @@ export type DaemonLifecycleResult = {
209
210
  updateState?: DaemonUpdateState;
210
211
  logs?: string[];
211
212
  };
212
- export type ReleaseJobStage = "accepted" | "running" | "gate_failed" | "deploy_failed" | "verify_failed" | "update_failed" | "notify_failed" | "dogfood_failed" | "succeeded" | "failed";
213
+ export type ReleaseJobStage = "accepted" | "running" | "gate_failed" | "deploy_failed" | "verify_failed" | "publish_failed" | "update_failed" | "notify_failed" | "dogfood_failed" | "succeeded" | "failed";
213
214
  export type ReleaseProfile = {
214
215
  name: string;
215
216
  releaseSessionTags: string[];
@@ -11,6 +11,7 @@ export type ControllerClientMessage = {
11
11
  name: string;
12
12
  model?: string;
13
13
  encryptedMetadata?: EncryptedEnvelope;
14
+ controllerEncryptedMetadata?: EncryptedEnvelope;
14
15
  } | {
15
16
  type: "controller:prompt";
16
17
  requestId: string;
@@ -33,6 +34,7 @@ export type ControllerClientMessage = {
33
34
  requestId: string;
34
35
  sessionId: string;
35
36
  encryptedMetadata: EncryptedEnvelope;
37
+ controllerEncryptedMetadata?: EncryptedEnvelope;
36
38
  } | {
37
39
  type: "controller:optionResponse";
38
40
  requestId: string;
@@ -142,6 +144,7 @@ export type ControllerClientMessage = {
142
144
  runtimeSessionId: string;
143
145
  name: string;
144
146
  encryptedMetadata?: EncryptedEnvelope;
147
+ controllerEncryptedMetadata?: EncryptedEnvelope;
145
148
  } | {
146
149
  type: "controller:rewind";
147
150
  requestId: string;
@@ -154,6 +157,7 @@ export type ControllerClientMessage = {
154
157
  from: string;
155
158
  name: string;
156
159
  encryptedMetadata?: EncryptedEnvelope;
160
+ controllerEncryptedMetadata?: EncryptedEnvelope;
157
161
  } | {
158
162
  type: "controller:renameSession";
159
163
  requestId: string;
@@ -311,6 +315,7 @@ export type MachineClientMessage = {
311
315
  messageId?: string;
312
316
  transcriptPending?: boolean;
313
317
  encryptedMetadata?: EncryptedEnvelope;
318
+ controllerEncryptedMetadata?: EncryptedEnvelope;
314
319
  } | {
315
320
  type: "machine:sessionRenamed";
316
321
  requestId: string;
@@ -493,6 +498,7 @@ export type MachineCommand = {
493
498
  name: string;
494
499
  sourceEncryptedMetadata?: EncryptedEnvelope;
495
500
  encryptedMetadata?: EncryptedEnvelope;
501
+ controllerEncryptedMetadata?: EncryptedEnvelope;
496
502
  } | {
497
503
  type: "machine:renameSession";
498
504
  requestId: string;
@@ -12,6 +12,7 @@ const controllerMessageSchema = z.discriminatedUnion("type", [
12
12
  name: z.string().min(1),
13
13
  model: z.string().min(1).optional(),
14
14
  encryptedMetadata: encryptedEnvelopeSchema.optional(),
15
+ controllerEncryptedMetadata: encryptedEnvelopeSchema.optional(),
15
16
  }),
16
17
  z.object({
17
18
  type: z.literal("controller:prompt"),
@@ -37,6 +38,7 @@ const controllerMessageSchema = z.discriminatedUnion("type", [
37
38
  requestId: z.string().min(1),
38
39
  sessionId: z.string().min(1),
39
40
  encryptedMetadata: encryptedEnvelopeSchema,
41
+ controllerEncryptedMetadata: encryptedEnvelopeSchema.optional(),
40
42
  }),
41
43
  z.object({
42
44
  type: z.literal("controller:optionResponse"),
@@ -160,6 +162,7 @@ const controllerMessageSchema = z.discriminatedUnion("type", [
160
162
  runtimeSessionId: z.string().min(1),
161
163
  name: z.string().min(1),
162
164
  encryptedMetadata: encryptedEnvelopeSchema.optional(),
165
+ controllerEncryptedMetadata: encryptedEnvelopeSchema.optional(),
163
166
  }),
164
167
  z.object({
165
168
  type: z.literal("controller:rewind"),
@@ -174,6 +177,7 @@ const controllerMessageSchema = z.discriminatedUnion("type", [
174
177
  from: z.string().min(1),
175
178
  name: z.string().min(1),
176
179
  encryptedMetadata: encryptedEnvelopeSchema.optional(),
180
+ controllerEncryptedMetadata: encryptedEnvelopeSchema.optional(),
177
181
  }),
178
182
  z.object({
179
183
  type: z.literal("controller:renameSession"),
@@ -372,6 +376,7 @@ const machineMessageSchema = z.discriminatedUnion("type", [
372
376
  messageId: z.string().min(1).optional(),
373
377
  transcriptPending: z.boolean().optional(),
374
378
  encryptedMetadata: encryptedEnvelopeSchema.optional(),
379
+ controllerEncryptedMetadata: encryptedEnvelopeSchema.optional(),
375
380
  }),
376
381
  z.object({
377
382
  type: z.literal("machine:sessionRenamed"),
@@ -580,6 +585,7 @@ const machineCommandSchema = z.discriminatedUnion("type", [
580
585
  name: z.string().min(1),
581
586
  sourceEncryptedMetadata: encryptedEnvelopeSchema.optional(),
582
587
  encryptedMetadata: encryptedEnvelopeSchema.optional(),
588
+ controllerEncryptedMetadata: encryptedEnvelopeSchema.optional(),
583
589
  }),
584
590
  z.object({
585
591
  type: z.literal("machine:renameSession"),