@cydm/happy-elves 0.1.0-beta.489 → 0.1.0-beta.490

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.489",
3
+ "version": "0.1.0-beta.490",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -177,10 +177,14 @@ export async function waitForInboundPromptStart(requestId, timeoutMs) {
177
177
  }
178
178
  export function retryablePreStartPromptError(code) {
179
179
  // Internal queue/schedule admission can reset deterministic pre-start
180
- // failures because no runtime turn was durably materialized.
180
+ // failures because no runtime turn was durably materialized. A queue gate
181
+ // reports CREW_SESSION_BUSY before the provider is touched; retaining that
182
+ // terminal response under the stable queue prompt request id would make the
183
+ // same FIFO head replay its own old rejection forever.
181
184
  // Controller packet replay is narrower and only resets explicit
182
185
  // SESSION_BUSY admission races in the inbound dispatcher.
183
186
  return code === "SESSION_BUSY" ||
187
+ code === "CREW_SESSION_BUSY" ||
184
188
  code === "TURN_START_FAILED" ||
185
189
  code === "PROMPT_ADMISSION_TIMEOUT" ||
186
190
  code === "CREW_SESSION_STATUS_UNAVAILABLE" ||
@@ -76,14 +76,17 @@ async function refreshNativeHistory(session, config, requestId, strict, orchestr
76
76
  // explicit adoption status because they were created before the
77
77
  // native-history state machine existed. Complete that migration on
78
78
  // the normal load/read path, preserving the existing HE thread id.
79
- if (metadata.headlessNativeAdoptionStatus !== "complete" && Array.isArray(metadata.nativeHistoryTurnIds)) {
79
+ const needsManagedOwnershipRepair = metadata.nativeHandoff === "manual-sequential" && metadata.nativeHistoryChangedExternally !== true;
80
+ if (needsManagedOwnershipRepair || (metadata.headlessNativeAdoptionStatus !== "complete" && Array.isArray(metadata.nativeHistoryTurnIds))) {
80
81
  await entry.client.adoptThread({
81
82
  threadId: session.runtimeBinding.threadId,
82
83
  providerSessionId,
83
84
  cwd: session.cwd,
84
85
  provider: entry.provider,
85
86
  nativeIdentity: expectedNativeIdentity,
87
+ sessionContext: { role: "crew" },
86
88
  manualHandoff: true,
89
+ managedHandoff: true,
87
90
  });
88
91
  }
89
92
  }
@@ -1003,8 +1003,12 @@ function promptFailureCode(phase, error) {
1003
1003
  return "PROMPT_DECRYPT_FAILED";
1004
1004
  if (phase === "decrypt-memory-options" && isCryptoOperationFailure(error))
1005
1005
  return "MEMORY_OPTIONS_DECRYPT_FAILED";
1006
- if (phase === "start-turn" || phase === "attach-turn" || phase === "persist-runtime-start")
1006
+ if (phase === "start-turn" || phase === "attach-turn" || phase === "persist-runtime-start") {
1007
+ const code = structuredPromptErrorCode(error);
1008
+ if (code && knownPromptStartErrorCode(code))
1009
+ return code;
1007
1010
  return "TURN_START_FAILED";
1011
+ }
1008
1012
  if (phase === "stream-turn" || phase === "await-turn-result")
1009
1013
  return "TURN_RUNTIME_FAILED";
1010
1014
  if (phase === "refresh-runtime-metadata")
@@ -1013,6 +1017,20 @@ function promptFailureCode(phase, error) {
1013
1017
  return "SESSION_EVENT_EMIT_FAILED";
1014
1018
  return "PROMPT_EXECUTION_FAILED";
1015
1019
  }
1020
+ function structuredPromptErrorCode(error) {
1021
+ if (!error || typeof error !== "object" || !("code" in error))
1022
+ return undefined;
1023
+ const code = error.code;
1024
+ return typeof code === "string" && code.trim() ? code : undefined;
1025
+ }
1026
+ function knownPromptStartErrorCode(code) {
1027
+ return code === "NATIVE_HANDOFF_RECONFIRM_REQUIRED" ||
1028
+ code === "NATIVE_HISTORY_CONFLICT" ||
1029
+ code === "CAPABILITY_UNSUPPORTED" ||
1030
+ code === "SESSION_BUSY" ||
1031
+ code === "RUNTIME_UNAVAILABLE" ||
1032
+ code === "REQUEST_TIMEOUT";
1033
+ }
1016
1034
  function promptFailureMessage(code, error) {
1017
1035
  if (code === "PROMPT_DECRYPT_FAILED") {
1018
1036
  return "Failed to decrypt the prompt for this daemon. Re-pair the daemon with the correct account secret.";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves-daemon",
3
- "version": "0.1.0-beta.489",
3
+ "version": "0.1.0-beta.490",
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.489",
3
+ "version": "0.1.0-beta.490",
4
4
  "description": "Remote controller for local coding agents with hosted or self-hosted relay support.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -190,7 +190,7 @@ class ClaudeHeadlessRuntimeProvider {
190
190
  if (takeoverState === "active") {
191
191
  throw new HeadlessRuntimeCoreError("INVALID_REQUEST", `Claude native session ${providerSessionId} is active or awaiting approval; safe takeover is not supported.`);
192
192
  }
193
- if (takeoverState === "unknown" && input.manualHandoff !== true) {
193
+ if (takeoverState === "unknown" && input.manualHandoff !== true && input.managedHandoff !== true) {
194
194
  throw new HeadlessRuntimeCoreError("CAPABILITY_UNSUPPORTED", `Claude native session ${providerSessionId} has no verifiable idle/takeover state in the configured SDK.`);
195
195
  }
196
196
  if (input.manualHandoff === true && input.manualHandoffProof?.updatedAt !== undefined && typeof info.lastModified === "number" && info.lastModified > input.manualHandoffProof.updatedAt) {
@@ -200,7 +200,7 @@ class ClaudeHeadlessRuntimeProvider {
200
200
  const history = claudeNativeHistory(providerSessionId, info, messages);
201
201
  this.sessionsByThread.set(input.thread.id, providerSessionId);
202
202
  this.startedThreads.add(input.thread.id);
203
- return { providerSessionId, history, ...(input.manualHandoff ? { providerMetadata: { nativeHandoff: "manual-sequential" } } : {}) };
203
+ return { providerSessionId, history, ...(input.manualHandoff && input.managedHandoff !== true ? { providerMetadata: { nativeHandoff: "manual-sequential" } } : {}) };
204
204
  }
205
205
  async readNativeHistory(input) {
206
206
  if (this.transport !== "sdk" || !input.thread.providerSessionId) {
@@ -227,7 +227,7 @@ class CodexHeadlessRuntimeProvider {
227
227
  if (takeoverState === "active") {
228
228
  throw new HeadlessRuntimeCoreError("INVALID_REQUEST", `Codex native session ${input.thread.providerSessionId} is active or awaiting approval; safe takeover is not supported.`);
229
229
  }
230
- if (takeoverState === "unknown" && input.manualHandoff !== true) {
230
+ if (takeoverState === "unknown" && input.manualHandoff !== true && input.managedHandoff !== true) {
231
231
  throw new HeadlessRuntimeCoreError("CAPABILITY_UNSUPPORTED", `Codex native session ${input.thread.providerSessionId} has no verifiable idle/takeover state in the configured app-server.`);
232
232
  }
233
233
  const nativeUpdatedAt = epochMs(nativeThread.updatedAt ?? nativeThread.updated_at ?? nativeThread.recencyAt ?? nativeThread.recency_at);
@@ -259,7 +259,7 @@ class CodexHeadlessRuntimeProvider {
259
259
  const binding = codexProviderThreadBinding(input.thread.providerSessionId, input.thread.providerMetadata, input.thread.model, input.thread.thinkingLevel, codexClientProfileForThread(input.thread));
260
260
  return {
261
261
  ...binding,
262
- ...(input.manualHandoff ? { providerMetadata: { ...binding.providerMetadata, nativeHandoff: "manual-sequential" } } : {}),
262
+ ...(input.manualHandoff && input.managedHandoff !== true ? { providerMetadata: { ...binding.providerMetadata, nativeHandoff: "manual-sequential" } } : {}),
263
263
  history: nativeHistory,
264
264
  };
265
265
  }
@@ -19,6 +19,7 @@ export type HeadlessRuntimeProvider = {
19
19
  startThread?(input: HeadlessRuntimeProviderThreadInput): Promise<HeadlessRuntimeProviderThreadBinding | void>;
20
20
  adoptThread?(input: HeadlessRuntimeProviderThreadInput & {
21
21
  manualHandoff?: boolean;
22
+ managedHandoff?: boolean;
22
23
  manualHandoffProof?: AdoptThreadParams["manualHandoffProof"];
23
24
  }): Promise<HeadlessRuntimeProviderThreadBinding | void>;
24
25
  validateNativeHandoff?(input: {
@@ -155,6 +155,12 @@ export class HeadlessRuntimeCore {
155
155
  if (!this.provider.adoptThread) {
156
156
  throw new HeadlessRuntimeCoreError("CAPABILITY_UNSUPPORTED", `Provider ${this.provider.id} does not support native session adoption.`);
157
157
  }
158
+ if (params.managedHandoff === true) {
159
+ const existing = this.projection.getThread(params.threadId);
160
+ if (!existing || existing.sessionContext?.role !== "crew") {
161
+ throw new HeadlessRuntimeCoreError("INVALID_REQUEST", "Managed native adoption is restricted to an existing persisted Crew-owned Headless thread.");
162
+ }
163
+ }
158
164
  const key = nativeAdoptionOperationKey(params.threadId, params.providerSessionId);
159
165
  const activeForThread = this.activeNativeAdoptionsByThread.get(params.threadId);
160
166
  if (activeForThread)
@@ -200,6 +206,29 @@ export class HeadlessRuntimeCore {
200
206
  return await this.adoptLegacyLoadedThread(existing, params, key);
201
207
  }
202
208
  assertNativeAdoptionIdentity(existing, params, this.provider.id);
209
+ const existingMetadata = recordFrom(existing.providerMetadata);
210
+ // A legacy orchestration Crew was already HE-owned before the native
211
+ // history migration ran. R5 could materialize that thread through the
212
+ // provider's manual takeover path, which left the external-handoff
213
+ // fence on an otherwise HE-owned Crew. Allow the daemon to durably
214
+ // reclassify that one managed binding without weakening native-session
215
+ // fencing for ordinary source:"native" threads.
216
+ if (params.managedHandoff === true &&
217
+ existingMetadata.nativeHandoff === "manual-sequential" &&
218
+ existingMetadata.nativeHistoryChangedExternally !== true) {
219
+ if (existing.sessionContext?.role !== "crew" || normalizedSessionContext(params.sessionContext)?.role !== "crew") {
220
+ throw new HeadlessRuntimeCoreError("INVALID_REQUEST", "Managed native adoption is restricted to an existing Crew-owned Headless thread.");
221
+ }
222
+ const nextMetadata = { ...existingMetadata };
223
+ delete nextMetadata.nativeHandoff;
224
+ delete nextMetadata.nativeHandoffProof;
225
+ const managedThread = { ...existing, providerMetadata: nextMetadata, updatedAt: this.now() };
226
+ await this.appendEvent("thread/nativeHandoffManaged", {
227
+ threadId: managedThread.id,
228
+ thread: managedThread,
229
+ });
230
+ return { thread: this.requireThread(managedThread.id) };
231
+ }
203
232
  const adoptionStatus = nativeAdoptionStatus(existing.providerMetadata);
204
233
  if (adoptionStatus === "unknown" || adoptionStatus === "reserved" || adoptionStatus === "importing") {
205
234
  throw new HeadlessRuntimeCoreError("REQUEST_TIMEOUT", `Native session adoption for ${params.providerSessionId} is incomplete and requires reconciliation before retry.`);
@@ -278,6 +307,7 @@ export class HeadlessRuntimeCore {
278
307
  thread,
279
308
  params: params,
280
309
  manualHandoff: params.manualHandoff === true,
310
+ ...(params.managedHandoff ? { managedHandoff: true } : {}),
281
311
  ...(params.manualHandoffProof ? { manualHandoffProof: params.manualHandoffProof } : {}),
282
312
  });
283
313
  }
@@ -357,6 +387,7 @@ export class HeadlessRuntimeCore {
357
387
  thread: identityReady,
358
388
  params: params,
359
389
  manualHandoff: true,
390
+ ...(params.managedHandoff ? { managedHandoff: true } : {}),
360
391
  ...(params.manualHandoffProof ? { manualHandoffProof: params.manualHandoffProof } : {}),
361
392
  });
362
393
  }
@@ -2902,6 +2933,9 @@ function assertNativeAdoptionParamsMatch(left, right, providerId) {
2902
2933
  if (leftProvider !== rightProvider || left.model !== right.model || left.thinkingLevel !== right.thinkingLevel) {
2903
2934
  throw new HeadlessRuntimeCoreError("INVALID_REQUEST", "Native session adoption is already in flight with different provider parameters.");
2904
2935
  }
2936
+ if (left.managedHandoff !== right.managedHandoff) {
2937
+ throw new HeadlessRuntimeCoreError("INVALID_REQUEST", "Native session adoption is already in flight with a different ownership mode.");
2938
+ }
2905
2939
  const leftRole = normalizedSessionContext(left.sessionContext)?.role;
2906
2940
  const rightRole = normalizedSessionContext(right.sessionContext)?.role;
2907
2941
  if (leftRole !== rightRole) {
@@ -185,6 +185,7 @@ async function dispatch(core, request) {
185
185
  ...(typeof params.nativeIdentity === "string" && params.nativeIdentity.trim() ? { nativeIdentity: params.nativeIdentity.trim() } : {}),
186
186
  ...(isSessionContext(params.sessionContext) || params.sessionContext === null ? { sessionContext: params.sessionContext } : {}),
187
187
  ...(params.manualHandoff === true ? { manualHandoff: true } : {}),
188
+ ...(params.managedHandoff === true ? { managedHandoff: true } : {}),
188
189
  ...(isManualHandoffProof(params.manualHandoffProof) ? { manualHandoffProof: params.manualHandoffProof } : {}),
189
190
  });
190
191
  case "thread/native-sync":
@@ -263,6 +263,13 @@ export type AdoptThreadParams = {
263
263
  sessionContext?: ThreadSessionContext | null;
264
264
  /** One-time user confirmation that the independent native client was stopped. */
265
265
  manualHandoff?: boolean;
266
+ /**
267
+ * The native session was created and is owned by Headless/Happy Elves. This
268
+ * still uses the provider's takeover path for legacy recovery, but must not
269
+ * turn the thread into an externally-owned manual handoff that requires a
270
+ * user proof before every subsequent HE turn.
271
+ */
272
+ managedHandoff?: boolean;
266
273
  /** Scope and history version observed by the user before confirming handoff. */
267
274
  manualHandoffProof?: {
268
275
  identity: string;