@zq-silk/yui 2.7.1 → 2.8.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.
- package/dist/cli/commandCatalog.js +8 -7
- package/dist/cli/updateCommand.js +3 -0
- package/dist/commands/configCommands.js +1 -0
- package/dist/commands/globalRoleCommands.js +42 -35
- package/dist/commands/inboxCommands.js +60 -40
- package/dist/commands/taskCommands.js +7 -3
- package/dist/commands/taskRoleRuntimeStatus.js +2 -0
- package/dist/context/runContextPack.js +48 -3
- package/dist/context/taskContext.js +36 -12
- package/dist/controlPlaneCli.js +46 -12
- package/dist/controller/controller.js +5 -0
- package/dist/controller/fileSchedulerStoreAdapter.js +9 -4
- package/dist/controller/globalInputDelivery.js +17 -5
- package/dist/controller/globalRuntimeAttention.js +135 -5
- package/dist/controller/runtime.js +4 -0
- package/dist/controller/structuredProviderObservation.js +51 -15
- package/dist/controller/taskHostTerminalRecovery.js +33 -0
- package/dist/executor/fileRoleLaunchPlanner.js +4 -9
- package/dist/message/inboxReceipt.js +30 -0
- package/dist/message/taskInboxOwnership.js +25 -3
- package/dist/models/modelManagement.js +469 -0
- package/dist/models/modelManagementConfig.js +46 -0
- package/dist/models/modelManagementFiles.js +90 -0
- package/dist/models/modelManagementHttp.js +90 -0
- package/dist/models/modelManagementTypes.js +1 -0
- package/dist/output/boundedRead.js +43 -14
- package/dist/runtime/agentEndpointIdentity.js +8 -4
- package/dist/runtime/agentEndpointOwnership.js +5 -6
- package/dist/runtime/agentHost.js +62 -10
- package/dist/runtime/agentHostImplementation.js +18 -0
- package/dist/runtime/managedCaller.js +25 -1
- package/dist/runtime/managedIdentity.js +1 -1
- package/dist/runtime/providerRetry.js +19 -1
- package/dist/runtime/sessionCaller.js +53 -0
- package/dist/storage/contextRecords.js +28 -0
- package/dist/storage/sqliteSchema.js +10 -0
- package/dist/storage/sqliteStore.js +9 -5
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/taskStore.js +4 -0
- package/dist/task/completionReadiness.js +3 -3
- package/dist/web/assets/assetManifest.js +28 -2
- package/dist/web/assets/client/app/manage.js +6 -0
- package/dist/web/assets/client/app/models.js +20 -0
- package/dist/web/assets/client/app/settings.js +41 -9
- package/dist/web/assets/client/i18n/en.js +180 -2
- package/dist/web/assets/client/i18n/zh-CN.js +178 -2
- package/dist/web/assets/client/index.js +8 -0
- package/dist/web/assets/client/layout/manageShell.js +94 -0
- package/dist/web/assets/client/lib/api.js +13 -1
- package/dist/web/assets/client/lib/i18n.js +5 -3
- package/dist/web/assets/client/views/dock/conversation.js +231 -80
- package/dist/web/assets/client/views/models.js +777 -0
- package/dist/web/assets/client/views/skills.js +22 -15
- package/dist/web/assets/shared/icons.js +2 -0
- package/dist/web/assets/shared/manageNav.js +13 -0
- package/dist/web/assets/shell/manage.js +54 -0
- package/dist/web/assets/shell/models.js +7 -0
- package/dist/web/assets/shell/settings.js +1 -35
- package/dist/web/assets/shell/settingsContent.js +30 -0
- package/dist/web/assets/shell/sidebar.js +1 -0
- package/dist/web/assets/styles/index.js +6 -0
- package/dist/web/assets/styles/views/conversation.js +133 -0
- package/dist/web/assets/styles/views/manage.js +71 -0
- package/dist/web/assets/styles/views/models.js +129 -0
- package/dist/web/assets/styles/views/settings.js +28 -14
- package/dist/web/assets/styles/views/sidebar.js +1 -0
- package/dist/web/webServer.js +59 -9
- package/docs/cli-information-contract.md +42 -11
- package/docs/cli-information-contract.zh-CN.md +28 -7
- package/docs/managed-turn-and-session-runtime.md +47 -1
- package/docs/managed-turn-and-session-runtime.zh-CN.md +32 -0
- package/docs/release-workflow.md +58 -7
- package/docs/release-workflow.zh-CN.md +39 -4
- package/docs/storage-baseline.md +6 -1
- package/docs/storage-baseline.zh-CN.md +4 -1
- package/docs/testing/review-evaluation.md +148 -0
- package/docs/testing/review-evaluation.zh-CN.md +117 -0
- package/docs/testing/verification-levels.md +6 -2
- package/docs/testing/verification-levels.zh-CN.md +3 -0
- package/package.json +1 -1
- package/skills/yui-leader/VERSION +1 -1
- package/skills/yui-leader/references/execution.md +29 -0
- package/skills/yui-leader/references/replicated-execution.md +7 -0
- package/skills/yui-operator/SKILL.md +5 -1
- package/skills/yui-operator/VERSION +1 -1
- package/skills/yui-reviewer/SKILL.md +35 -0
- package/skills/yui-reviewer/VERSION +1 -1
- package/skills/yui-runtime/SKILL.md +53 -7
- package/skills/yui-runtime/VERSION +1 -1
- package/skills/yui-runtime/references/recovery.md +24 -5
|
@@ -10,10 +10,15 @@ import { FileRuntimeEventInbox } from "./runtimeEventInbox.js";
|
|
|
10
10
|
import { AGENT_HOST_EVENT_PROTOCOL } from "../runtime/agentHostProtocol.js";
|
|
11
11
|
import { redactAgentErrorText } from "../runtime/agentError.js";
|
|
12
12
|
let structuredSequence = 0;
|
|
13
|
-
|
|
13
|
+
const deliveryFailures = new Map();
|
|
14
14
|
// Only live diagnostics for this process's immutable files, not another queue.
|
|
15
15
|
// No payload, retry, or authoritative delivery state is kept in this map.
|
|
16
16
|
const emittedEvents = new Map();
|
|
17
|
+
// Only facts whose immutable Inbox write has not succeeded. The original
|
|
18
|
+
// process must retain these bytes during a filesystem fault: another direct
|
|
19
|
+
// native turn can arrive without Yui admission and replace a last-turn cache.
|
|
20
|
+
// No new durable queue or execution retry; discard only after durable enqueue.
|
|
21
|
+
const unpersistedEvents = new Map();
|
|
17
22
|
/** Retained in the existing Host Inbox; it never settles a Turn or a resource. */
|
|
18
23
|
export async function publishStructuredProviderDiagnostic(input) {
|
|
19
24
|
const diagnostic = input.diagnostic;
|
|
@@ -44,6 +49,7 @@ export async function publishStructuredProviderDiagnostic(input) {
|
|
|
44
49
|
/** A live transport observation, independent of Provider state and Run truth. */
|
|
45
50
|
export function structuredProviderEventDelivery(home, environment, nativeSessionId) {
|
|
46
51
|
try {
|
|
52
|
+
const deliveryFailure = deliveryFailures.get(deliveryKey(home, environment));
|
|
47
53
|
const inbox = new FileRuntimeEventInbox(home);
|
|
48
54
|
const pending = [];
|
|
49
55
|
for (const [id, entry] of emittedEvents) {
|
|
@@ -57,16 +63,39 @@ export function structuredProviderEventDelivery(home, environment, nativeSession
|
|
|
57
63
|
&& (nativeSessionId === undefined || entry.nativeSessionId === nativeSessionId))
|
|
58
64
|
pending.push(entry);
|
|
59
65
|
}
|
|
66
|
+
const retained = pendingPersistence(home, environment);
|
|
60
67
|
return {
|
|
61
|
-
pending: pending.length,
|
|
62
|
-
|
|
68
|
+
pending: pending.length + retained.length,
|
|
69
|
+
unpersisted: retained.length,
|
|
70
|
+
pendingTerminals: pending.filter(event => event.terminal).length
|
|
71
|
+
+ retained.filter(event => ["turn.completed", "turn.failed", "turn.cancelled"].includes(event.observation.kind)).length,
|
|
63
72
|
...(deliveryFailure === undefined ? {} : { failure: deliveryFailure })
|
|
64
73
|
};
|
|
65
74
|
}
|
|
66
75
|
catch (error) {
|
|
67
|
-
|
|
76
|
+
const retained = pendingPersistence(home, environment);
|
|
77
|
+
return {
|
|
78
|
+
pending: retained.length, unpersisted: retained.length,
|
|
79
|
+
pendingTerminals: retained.filter(event => ["turn.completed", "turn.failed", "turn.cancelled"].includes(event.observation.kind)).length,
|
|
80
|
+
failure: deliveryFault("persistence", error)
|
|
81
|
+
};
|
|
68
82
|
}
|
|
69
83
|
}
|
|
84
|
+
function deliveryKey(home, environment) {
|
|
85
|
+
return JSON.stringify([home, environment.YUI_TASK_ID, environment.YUI_ROLE, environment.YUI_AGENT_ID]);
|
|
86
|
+
}
|
|
87
|
+
function pendingPersistence(home, environment) {
|
|
88
|
+
return [...unpersistedEvents.values()].filter(event => event.home === home
|
|
89
|
+
&& event.observation.fence.taskId === environment.YUI_TASK_ID
|
|
90
|
+
&& event.observation.fence.roleName === environment.YUI_ROLE
|
|
91
|
+
&& event.observation.fence.agentId === environment.YUI_AGENT_ID);
|
|
92
|
+
}
|
|
93
|
+
/** Retry only original native facts, under the original Host's control fence. */
|
|
94
|
+
export async function replayStructuredProviderPersistence(home, environment) {
|
|
95
|
+
const retained = pendingPersistence(home, environment).map(event => event.observation);
|
|
96
|
+
await persistAndApply(home, [], environment);
|
|
97
|
+
return retained;
|
|
98
|
+
}
|
|
70
99
|
function deliveryFault(stage, error) {
|
|
71
100
|
return {
|
|
72
101
|
stage, detail: redactAgentErrorText(error instanceof Error ? error.message : String(error)),
|
|
@@ -412,28 +441,35 @@ function requireIdentity(value, label) {
|
|
|
412
441
|
}
|
|
413
442
|
async function persistAndApply(home, observations, environment, connection, startupRunId) {
|
|
414
443
|
const inbox = new FileRuntimeEventInbox(home);
|
|
444
|
+
const key = deliveryKey(home, environment);
|
|
415
445
|
const host = {
|
|
416
446
|
protocol: AGENT_HOST_EVENT_PROTOCOL,
|
|
417
447
|
adapterId: requireIdentity(environment.YUI_ADAPTER_ID, "Agent adapter id"),
|
|
418
448
|
workspace: requireIdentity(environment.YUI_WORKSPACE, "Yui workspace"),
|
|
419
449
|
...(connection === undefined ? {} : { connection })
|
|
420
450
|
};
|
|
421
|
-
|
|
451
|
+
const source = { ...host, ...(startupRunId === undefined ? {} : { startupRunId }) };
|
|
452
|
+
for (const observation of observations) {
|
|
453
|
+
unpersistedEvents.set(observation.eventId, { home, observation, host: source });
|
|
454
|
+
}
|
|
455
|
+
const events = [];
|
|
422
456
|
try {
|
|
423
457
|
structuredProviderEventDelivery(home, environment); // prune ACKed local diagnostics
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
for (const event of events)
|
|
458
|
+
for (const retained of pendingPersistence(home, environment)) {
|
|
459
|
+
const event = inbox.enqueueObservation(retained.observation, retained.host).event;
|
|
460
|
+
events.push(event);
|
|
461
|
+
unpersistedEvents.delete(retained.observation.eventId);
|
|
429
462
|
emittedEvents.set(event.id, {
|
|
430
463
|
home, taskId: event.taskId, roleName: event.observation.fence.roleName,
|
|
431
464
|
nativeSessionId: event.observation.fence.nativeSessionId,
|
|
432
465
|
terminal: ["turn.completed", "turn.failed", "turn.cancelled"].includes(event.observation.kind)
|
|
433
466
|
});
|
|
467
|
+
}
|
|
468
|
+
if (deliveryFailures.get(key)?.stage === "persistence")
|
|
469
|
+
deliveryFailures.delete(key);
|
|
434
470
|
}
|
|
435
471
|
catch (error) {
|
|
436
|
-
|
|
472
|
+
deliveryFailures.set(key, deliveryFault("persistence", error));
|
|
437
473
|
throw error;
|
|
438
474
|
}
|
|
439
475
|
// The immutable inbox is authoritative. During a release/update handover the
|
|
@@ -463,7 +499,7 @@ async function persistAndApply(home, observations, environment, connection, star
|
|
|
463
499
|
return;
|
|
464
500
|
// The native fact is already durable. An incompatible/refusing
|
|
465
501
|
// Controller is a reporting fault, not Provider execution failure.
|
|
466
|
-
|
|
502
|
+
deliveryFailures.set(key, deliveryFault("controller", error));
|
|
467
503
|
return;
|
|
468
504
|
}
|
|
469
505
|
// A fast Provider can accept the initial AgentRun before the scheduler call
|
|
@@ -472,12 +508,12 @@ async function persistAndApply(home, observations, environment, connection, star
|
|
|
472
508
|
// `deferred` therefore means "retained for replay", not delivery failure.
|
|
473
509
|
// The signal below schedules the replay after the transport transaction.
|
|
474
510
|
if (result.outcome !== "applied" && result.outcome !== "deferred") {
|
|
475
|
-
|
|
511
|
+
deliveryFailures.set(key, deliveryFault("controller", `Structured Provider observation was rejected: ${result.outcome ?? "unknown"}. Event: ${entry.id}`));
|
|
476
512
|
return;
|
|
477
513
|
}
|
|
478
514
|
}
|
|
479
|
-
if (
|
|
480
|
-
|
|
515
|
+
if (events.length > 0 && deliveryFailures.get(key)?.stage === "controller")
|
|
516
|
+
deliveryFailures.delete(key);
|
|
481
517
|
await signalController(home, hostTaskId(environment), requireIdentity(environment.YUI_ROLE, "Role"));
|
|
482
518
|
}
|
|
483
519
|
/** Capture only identities the Host actually received; the current Controller
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { AGENT_HOST_CONTROL_PROTOCOL, reconcileAgentHostTurn } from "../runtime/agentHost.js";
|
|
2
|
+
import { currentProviderConversation } from "../runtime/providerRuntimeIdentity.js";
|
|
3
|
+
import { providerRetryPending } from "../runtime/providerRetry.js";
|
|
4
|
+
/** Existing full reconciliation pass only; no new timer, input or Session. */
|
|
5
|
+
export async function recoverTaskHostTerminals(home, store, onError, reconcile = reconcileAgentHostTurn) {
|
|
6
|
+
for (const candidate of store.listRuntimeSessionCandidates({ scope: "task" })) {
|
|
7
|
+
const owner = candidate.owner;
|
|
8
|
+
if (owner.scope !== "task")
|
|
9
|
+
continue;
|
|
10
|
+
const binding = store.getTaskRoleSessionSet(owner.taskId, owner.roleName)?.providerBinding;
|
|
11
|
+
const turn = binding?.run;
|
|
12
|
+
if (binding === undefined || binding === null || turn == null || providerRetryPending(binding)
|
|
13
|
+
|| !["submitting", "accepted", "delivery-unknown"].includes(turn.status)
|
|
14
|
+
|| binding.authority.owner !== "controller" || binding.authority.holderId === undefined)
|
|
15
|
+
continue;
|
|
16
|
+
try {
|
|
17
|
+
const result = await reconcile({ home, ...owner, control: {
|
|
18
|
+
protocol: AGENT_HOST_CONTROL_PROTOCOL, type: "reconcile-turn",
|
|
19
|
+
nativeSessionId: currentProviderConversation(binding).conversationId,
|
|
20
|
+
attemptId: turn.attemptId, nativeTurnId: turn.nativeTurnId,
|
|
21
|
+
authority: { epoch: binding.authority.epoch, owner: "controller", holderId: binding.authority.holderId }
|
|
22
|
+
} });
|
|
23
|
+
if (result.outcome === "rejected" || result.reconciliation === "unsupported") {
|
|
24
|
+
throw new Error(result.failure?.detail
|
|
25
|
+
?? "Host terminal reconciliation is unsupported; retain the original input and inspect Host/Inbox evidence.");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
onError(new Error(`Task Host terminal recovery ${owner.taskId}/${owner.roleName}/${turn.attemptId}: `
|
|
30
|
+
+ `${error instanceof Error ? error.message : String(error)} No input was replayed.`, { cause: error }));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -259,15 +259,10 @@ export class FileRoleLaunchPlanner {
|
|
|
259
259
|
const existingSession = owner.scope === "task"
|
|
260
260
|
? this.store.getTaskRoleSessionSet(owner.taskId, role.name)?.sessions[input.agentId]
|
|
261
261
|
: this.store.getGlobalRoleSessionSet(role.name)?.sessions[input.agentId];
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
// while rewriting to current code would silently move a Session onto code it
|
|
267
|
-
// never started on. The Host that actually executes the implementation makes
|
|
268
|
-
// the decision — a reused live Host accepts its own generation, and a newly
|
|
269
|
-
// started Host running different code fails closed
|
|
270
|
-
// (`agentEndpointOwnership.pin` -> `requireBuiltinAgentEndpointImplementation`).
|
|
262
|
+
// Retain original launch provenance. The Host reports its actually loaded
|
|
263
|
+
// build separately and checks adapter/native identity, not build equality.
|
|
264
|
+
// A compatible new Host can resume this exact Session after safe release;
|
|
265
|
+
// this planner never silently replaces a live Host or native conversation.
|
|
271
266
|
const endpointImplementation = input.mode === "resume"
|
|
272
267
|
? validateAgentEndpointImplementation(existingSession.endpointImplementation)
|
|
273
268
|
: builtinAgentEndpointImplementation(binding.adapterId);
|
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import { requireKnownFields, requireText, requireTimestamp } from "../domain/validation.js";
|
|
2
2
|
import { isDeepStrictEqual } from "node:util";
|
|
3
|
+
/** Semantic handling and transport delivery are independent facts. Projections
|
|
4
|
+
* must not infer handling from delivery, a native terminal, or an empty queue. */
|
|
5
|
+
export function inboxHandlingState(receipt) {
|
|
6
|
+
return receipt?.ack !== undefined ? "handled" : receipt !== undefined ? "claimed-unacknowledged" : "unclaimed";
|
|
7
|
+
}
|
|
8
|
+
export function isGlobalInboxMessage(message) {
|
|
9
|
+
return message.inputControl !== undefined || message.interruptThen !== undefined;
|
|
10
|
+
}
|
|
11
|
+
/** Unhandled own-role input, including accepted and unknown delivery. This is
|
|
12
|
+
* NOT the scheduler's deliverable queue; selecting it never authorizes replay. */
|
|
13
|
+
export function pendingGlobalInboxMessages(messages) {
|
|
14
|
+
return messages.filter(message => isGlobalInboxMessage(message) && inboxHandlingState(message.inbox) !== "handled");
|
|
15
|
+
}
|
|
16
|
+
/** Original message evidence only. A missing receipt is not proof of
|
|
17
|
+
* non-submission; admission additionally inspects the exact native attempt. */
|
|
18
|
+
export function globalInboxDeliveryState(message) {
|
|
19
|
+
return message.control?.outcome ?? (message.delivery?.via === "provider" ? "accepted"
|
|
20
|
+
: message.delivery === undefined ? "unobserved" : "delivery-unknown");
|
|
21
|
+
}
|
|
3
22
|
export function validateInboxReceipt(receipt) {
|
|
4
23
|
if (receipt === undefined)
|
|
5
24
|
return;
|
|
@@ -30,3 +49,14 @@ export function assertInboxReceiptPreserved(previous, next) {
|
|
|
30
49
|
throw new Error("Inbox handling ownership changed; reread the original instead of overwriting its receipt.");
|
|
31
50
|
}
|
|
32
51
|
}
|
|
52
|
+
/** A receipt cannot silently acknowledge different content under the same ID.
|
|
53
|
+
* Operational delivery/Assignment fields may evolve with their existing
|
|
54
|
+
* guards, but handled originals must be submitted as new Messages to change.
|
|
55
|
+
*/
|
|
56
|
+
export function assertInboxOriginalPreserved(previous, next) {
|
|
57
|
+
assertInboxReceiptPreserved(previous?.inbox, next.inbox);
|
|
58
|
+
if (previous?.inbox !== undefined && (previous.body !== next.body || previous.kind !== next.kind
|
|
59
|
+
|| !isDeepStrictEqual(previous.author, next.author) || !isDeepStrictEqual(previous.inputControl, next.inputControl))) {
|
|
60
|
+
throw new Error("Inbox receipt binds the exact original Message; submit changed content as a new Message.");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
|
|
2
|
+
/** Pin mutable originals by their existing event identity, not wall-clock time.
|
|
3
|
+
* Both automatic notification and active consumption use the same fixed refs. */
|
|
4
|
+
export function pinMessageRevisions(store, taskId, refs) {
|
|
5
|
+
const pinned = [...refs];
|
|
6
|
+
for (const ref of refs) {
|
|
7
|
+
if (ref.type !== "message" || ref.taskId !== taskId)
|
|
8
|
+
continue;
|
|
9
|
+
const message = store.listMessages(taskId).find(message => message.id === ref.id);
|
|
10
|
+
const revision = message === undefined ? undefined : latestMessageUpdate(store, message);
|
|
11
|
+
if (revision !== undefined && !pinned.some(ref => ref.type === "event" && ref.id === revision.id)) {
|
|
12
|
+
pinned.push({ type: "event", taskId, id: revision.id });
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return pinned;
|
|
16
|
+
}
|
|
2
17
|
export function latestMessageUpdate(store, message) {
|
|
3
18
|
return store.listEvents(message.taskId).filter(event => event.type === "message.updated" && event.payload.messageId === message.id).at(-1);
|
|
4
19
|
}
|
|
@@ -9,10 +24,17 @@ export function isLeaderSteer(message) {
|
|
|
9
24
|
}
|
|
10
25
|
/** Derive ownership from original receipts, never from disposable mailboxes. */
|
|
11
26
|
export function taskMessageInboxReceipt(store, message) {
|
|
27
|
+
return message.inbox ?? messageRevisionWakes(store, message).find(wake => wake.inbox !== undefined)?.inbox;
|
|
28
|
+
}
|
|
29
|
+
/** Delivery suppresses repeat notification of that revision, but remains
|
|
30
|
+
* distinct from handling. The original fixed window stays in the own inbox. */
|
|
31
|
+
export function deliveredMessageWake(store, message) {
|
|
32
|
+
return messageRevisionWakes(store, message).find(wake => wake.status === "consumed");
|
|
33
|
+
}
|
|
34
|
+
function messageRevisionWakes(store, message) {
|
|
12
35
|
const revision = latestMessageUpdate(store, message);
|
|
13
|
-
return
|
|
14
|
-
&&
|
|
15
|
-
&& wake.refs?.some(ref => ref.type === "message" && ref.taskId === message.taskId && ref.id === message.id))?.inbox;
|
|
36
|
+
return store.listTaskWakes(message.taskId).filter(wake => (revision === undefined || wake.refs?.some(ref => ref.type === "event" && ref.taskId === message.taskId && ref.id === revision.id))
|
|
37
|
+
&& wake.refs?.some(ref => ref.type === "message" && ref.taskId === message.taskId && ref.id === message.id));
|
|
16
38
|
}
|
|
17
39
|
export function unfinishedLeaderInbox(store, taskId) {
|
|
18
40
|
return store.listTaskWakes(taskId).some(wake => wake.inbox !== undefined && wake.inbox.ack === undefined)
|