@cydm/happy-elves 0.1.0-beta.486 → 0.1.0-beta.487
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/apps/cli/dist/commands/lib/args.js +4 -0
- package/apps/cli/dist/commands/lib/usage.js +3 -3
- package/apps/cli/dist/commands/lib/workspace.d.ts +6 -1
- package/apps/cli/dist/commands/lib/workspace.js +5 -0
- package/apps/cli/dist/commands/session.js +54 -5
- package/apps/cli/package.json +1 -1
- package/apps/daemon/dist/orchestration/crew-queue.js +33 -2
- package/apps/daemon/dist/orchestration/handler.js +12 -2
- package/apps/daemon/dist/orchestration/store.d.ts +1 -0
- package/apps/daemon/dist/orchestration/store.js +12 -0
- package/apps/daemon/dist/relay/connection.js +61 -2
- package/apps/daemon/dist/runtime/headless-manager.js +39 -4
- package/apps/daemon/dist/runtime/headless-session-history.js +20 -1
- package/apps/daemon/dist/session/he-official/artifact-commands.js +9 -7
- package/apps/daemon/dist/session/he-official/common.d.ts +29 -2
- package/apps/daemon/dist/session/he-official/common.js +26 -11
- package/apps/daemon/dist/session/he-official/orchestration-commands.js +2 -2
- package/apps/daemon/dist/session/lifecycle.js +66 -3
- package/apps/daemon/dist/session/native-identity.js +17 -1
- package/apps/daemon/dist/session/prompt.d.ts +1 -0
- package/apps/daemon/dist/session/prompt.js +20 -5
- package/apps/daemon/dist/state.d.ts +5 -1
- package/apps/daemon/dist/state.js +28 -0
- package/apps/daemon/package.json +1 -1
- package/apps/relay/dist/http-routes.js +74 -78
- package/apps/relay/dist/index.js +12 -4
- package/apps/relay/dist/packet-store.js +9 -2
- package/apps/relay/dist/relay-context.d.ts +213 -3
- package/apps/relay/dist/relay-context.js +336 -6
- package/apps/relay/dist/relay-migration.d.ts +25 -2
- package/apps/relay/dist/relay-migration.js +27 -112
- package/apps/relay/dist/retention.d.ts +10 -0
- package/apps/relay/dist/retention.js +20 -0
- package/apps/relay/dist/storage-worker.d.ts +137 -0
- package/apps/relay/dist/storage-worker.js +490 -0
- package/apps/relay/dist/websocket.d.ts +4 -0
- package/apps/relay/dist/websocket.js +223 -106
- package/package.json +1 -1
- package/packages/client/dist/client.js +6 -1
- package/packages/client/dist/types.d.ts +1 -0
- package/packages/client/dist/workspace-shared-config.js +8 -3
- package/packages/headless-runtime/dist/codex-provider.js +76 -18
- package/packages/headless-runtime/dist/core.d.ts +1 -0
- package/packages/headless-runtime/dist/core.js +134 -6
- package/packages/shared/dist/packet-relay.d.ts +2 -0
- package/packages/shared/dist/packet-relay.js +1 -0
- package/packages/shared/dist/protocol.d.ts +1 -0
- package/packages/shared/dist/protocol.js +1 -0
|
@@ -33,12 +33,14 @@ const valueFlags = new Set([
|
|
|
33
33
|
"limit",
|
|
34
34
|
"machine",
|
|
35
35
|
"machine-id",
|
|
36
|
+
"native-identity",
|
|
36
37
|
"memory",
|
|
37
38
|
"memory-mode",
|
|
38
39
|
"message-id",
|
|
39
40
|
"memory-project",
|
|
40
41
|
"memory-top-n",
|
|
41
42
|
"name",
|
|
43
|
+
"observed-at",
|
|
42
44
|
"model",
|
|
43
45
|
"thinking-level",
|
|
44
46
|
"mode",
|
|
@@ -69,6 +71,7 @@ const valueFlags = new Set([
|
|
|
69
71
|
"allow-sender",
|
|
70
72
|
"expires-in",
|
|
71
73
|
"format",
|
|
74
|
+
"history-version",
|
|
72
75
|
"loops",
|
|
73
76
|
"machines",
|
|
74
77
|
"sessions",
|
|
@@ -112,6 +115,7 @@ const booleanFlags = new Set([
|
|
|
112
115
|
"allow-any-sender",
|
|
113
116
|
"app-secret-stdin",
|
|
114
117
|
"confirm-risk",
|
|
118
|
+
"confirm-native-handoff",
|
|
115
119
|
"copy-tags",
|
|
116
120
|
"detach",
|
|
117
121
|
"disable-reaction",
|
|
@@ -53,7 +53,7 @@ Check local controller config, relay reachability, and daemon state.
|
|
|
53
53
|
happy-elves session tag remove <sessionId> --tag <tag> --json
|
|
54
54
|
happy-elves session tag list <sessionId> --json
|
|
55
55
|
happy-elves session history --machine <machineId> [--cwd <path>] [--agent <agent>] [--include-archived] [--limit 20] --json
|
|
56
|
-
happy-elves session continue <runtimeSessionId> --machine <machineId> [--name <name>] --json
|
|
56
|
+
happy-elves session continue <runtimeSessionId> --machine <machineId> [--name <name>] [--confirm-native-handoff] [--observed-at <ms>] [--history-version <version>] --json
|
|
57
57
|
happy-elves session status <sessionId> [--verbose] [--json]
|
|
58
58
|
happy-elves session diagnose <sessionId> [--limit 200] [--repair-head] [--json]
|
|
59
59
|
happy-elves session close <sessionId> [--reason <reason>] --json
|
|
@@ -271,7 +271,7 @@ Core commands:
|
|
|
271
271
|
session list [--machine <machineId>] [--cwd <path>] [--limit 20] [--json]
|
|
272
272
|
session create --machine <machineId> [--agent codex] [--cwd <path>] [--name <name>] --json
|
|
273
273
|
session history --machine <machineId> [--cwd <path>] [--limit 20] --json
|
|
274
|
-
session continue <runtimeSessionId> --machine <machineId> --json
|
|
274
|
+
session continue <runtimeSessionId> --machine <machineId> [--confirm-native-handoff] [--observed-at <ms>] [--history-version <version>] --json
|
|
275
275
|
session status <sessionId> [--verbose] [--json]
|
|
276
276
|
session diagnose <sessionId> [--limit 200] [--repair-head] [--json]
|
|
277
277
|
session close <sessionId> [--reason <reason>] --json
|
|
@@ -326,7 +326,7 @@ Usage:
|
|
|
326
326
|
|
|
327
327
|
Common flows:
|
|
328
328
|
Discover: machine list --json; session list --machine <machineId> --json
|
|
329
|
-
Continue: session history --machine <machineId> --json; session continue <runtimeSessionId> --machine <machineId> --json
|
|
329
|
+
Continue: session history --machine <machineId> --json; session continue <runtimeSessionId> --machine <machineId> --confirm-native-handoff --json
|
|
330
330
|
Execute: turn run <sessionId> --text <prompt> --json; turn result <sessionId> <turnId> --json
|
|
331
331
|
Branch: turn rewind <sessionId> <turnId> --json; turn fork <sessionId> <turnId> --name <name> --json
|
|
332
332
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ControllerClient } from "../../../../../packages/client/dist/index.js";
|
|
2
|
-
import type { HistoricalSessionSnapshot } from "../../../../../packages/shared/dist/index.js";
|
|
2
|
+
import type { HistoricalSessionSnapshot, HistoricalSessionWarning } from "../../../../../packages/shared/dist/index.js";
|
|
3
3
|
export type WorkspaceHistoricalSession = HistoricalSessionSnapshot & {
|
|
4
4
|
machineId: string;
|
|
5
5
|
machineName: string;
|
|
@@ -18,6 +18,11 @@ export declare function listWorkspaceHistoricalSessions(client: ControllerClient
|
|
|
18
18
|
code?: string;
|
|
19
19
|
message: string;
|
|
20
20
|
}>;
|
|
21
|
+
warnings: Array<{
|
|
22
|
+
machineId: string;
|
|
23
|
+
machineName: string;
|
|
24
|
+
warning: HistoricalSessionWarning;
|
|
25
|
+
}>;
|
|
21
26
|
machines: Array<{
|
|
22
27
|
machineId: string;
|
|
23
28
|
machineName: string;
|
|
@@ -18,6 +18,7 @@ export async function listWorkspaceHistoricalSessions(client, input) {
|
|
|
18
18
|
const machineName = projected.metadata.name ?? projected.metadata.hostname ?? machine.name;
|
|
19
19
|
try {
|
|
20
20
|
const sessions = [];
|
|
21
|
+
const warnings = [];
|
|
21
22
|
let cursor;
|
|
22
23
|
const seenCursors = new Set();
|
|
23
24
|
while (sessions.length < input.limit) {
|
|
@@ -30,6 +31,7 @@ export async function listWorkspaceHistoricalSessions(client, input) {
|
|
|
30
31
|
cursor,
|
|
31
32
|
});
|
|
32
33
|
sessions.push(...page.sessions);
|
|
34
|
+
warnings.push(...(page.warnings ?? []));
|
|
33
35
|
if (!page.hasMore || !page.nextCursor)
|
|
34
36
|
break;
|
|
35
37
|
if (seenCursors.has(page.nextCursor))
|
|
@@ -42,6 +44,7 @@ export async function listWorkspaceHistoricalSessions(client, input) {
|
|
|
42
44
|
.filter((session) => input.includeArchived === true || session.runtimeState !== "archived")
|
|
43
45
|
.map((session) => ({ ...session, machineId: machine.id, machineName })),
|
|
44
46
|
error: undefined,
|
|
47
|
+
warnings,
|
|
45
48
|
machine: { machineId: machine.id, machineName },
|
|
46
49
|
};
|
|
47
50
|
}
|
|
@@ -55,6 +58,7 @@ export async function listWorkspaceHistoricalSessions(client, input) {
|
|
|
55
58
|
code: enriched.code,
|
|
56
59
|
message: error instanceof Error ? error.message : String(error),
|
|
57
60
|
},
|
|
61
|
+
warnings: [],
|
|
58
62
|
machine: { machineId: machine.id, machineName },
|
|
59
63
|
};
|
|
60
64
|
}
|
|
@@ -65,6 +69,7 @@ export async function listWorkspaceHistoricalSessions(client, input) {
|
|
|
65
69
|
.sort((left, right) => right.updatedAt - left.updatedAt || left.runtimeSessionId.localeCompare(right.runtimeSessionId))
|
|
66
70
|
.slice(0, input.limit)),
|
|
67
71
|
errors: rows.flatMap((row) => row.error ? [row.error] : []),
|
|
72
|
+
warnings: rows.flatMap((row) => row.warnings.map((warning) => ({ machineId: row.machine.machineId, machineName: row.machine.machineName, warning }))),
|
|
68
73
|
machines: rows.map((row) => row.machine),
|
|
69
74
|
};
|
|
70
75
|
}
|
|
@@ -546,13 +546,23 @@ export async function handleSession({ domain, action, positional, flags }) {
|
|
|
546
546
|
returned: result.sessions.length,
|
|
547
547
|
hasMore: result.sessions.length >= limit,
|
|
548
548
|
},
|
|
549
|
+
warnings: result.warnings,
|
|
549
550
|
...(wantsVerbose(flags) ? { errors: result.errors, machines: result.machines } : {}),
|
|
550
551
|
}, { machineId });
|
|
551
552
|
}
|
|
552
553
|
else if (sessions.length === 0) {
|
|
554
|
+
for (const { machineName, warning } of result.warnings) {
|
|
555
|
+
console.log(`Warning: ${machineName} · ${warning.provider}: ${warning.message}`);
|
|
556
|
+
}
|
|
557
|
+
for (const error of result.errors) {
|
|
558
|
+
console.log(`Warning: ${error.machineName}: ${error.message}`);
|
|
559
|
+
}
|
|
553
560
|
console.log("No runtime historical sessions found.");
|
|
554
561
|
}
|
|
555
562
|
else {
|
|
563
|
+
for (const { machineName, warning } of result.warnings) {
|
|
564
|
+
console.log(`Warning: ${machineName} · ${warning.provider}: ${warning.message}`);
|
|
565
|
+
}
|
|
556
566
|
for (const session of sessions) {
|
|
557
567
|
const loaded = session.sessionId ?? "-";
|
|
558
568
|
const state = session.runtimeState === "archived" ? " archived" : "";
|
|
@@ -576,10 +586,25 @@ export async function handleSession({ domain, action, positional, flags }) {
|
|
|
576
586
|
includeArchived: true,
|
|
577
587
|
limit: 200,
|
|
578
588
|
});
|
|
579
|
-
const historical = result.sessions.find((session) => session.runtimeSessionId === runtimeSessionId)
|
|
580
|
-
|
|
589
|
+
const historical = result.sessions.find((session) => session.runtimeSessionId === runtimeSessionId)
|
|
590
|
+
?? result.sessions.find((session) => session.providerSessionId === runtimeSessionId && session.nativeIdentity);
|
|
591
|
+
if (!historical) {
|
|
592
|
+
const warning = result.warnings[0]?.warning;
|
|
593
|
+
if (warning) {
|
|
594
|
+
const code = warning.code === "permission-denied" ? "PROVIDER_PERMISSION_DENIED"
|
|
595
|
+
: warning.code === "unsupported" ? "PROVIDER_CAPABILITY_UNAVAILABLE"
|
|
596
|
+
: "PROVIDER_UNAVAILABLE";
|
|
597
|
+
throw new CliError(`${warning.provider}: ${warning.message}`, code);
|
|
598
|
+
}
|
|
581
599
|
throw new CliError(`Runtime session not found: ${runtimeSessionId}`, "RUNTIME_SESSION_NOT_FOUND");
|
|
582
|
-
|
|
600
|
+
}
|
|
601
|
+
// Imported native sessions are represented by a stable HE runtime id in
|
|
602
|
+
// the canonical headless row, while the provider UUID remains the
|
|
603
|
+
// durable native provenance. Accept either id, but retain the native
|
|
604
|
+
// handoff path whenever that provenance is present.
|
|
605
|
+
const isNative = historical.source === "native" || Boolean(historical.nativeIdentity && historical.providerSessionId);
|
|
606
|
+
const resolvedRuntimeSessionId = historical.runtimeSessionId;
|
|
607
|
+
if (historical.loadedSessionId && !isNative) {
|
|
583
608
|
ok("session.continue", {
|
|
584
609
|
sessionId: historical.loadedSessionId,
|
|
585
610
|
runtimeSessionId,
|
|
@@ -591,19 +616,43 @@ export async function handleSession({ domain, action, positional, flags }) {
|
|
|
591
616
|
const name = typeof flags.name === "string" && flags.name.trim()
|
|
592
617
|
? flags.name.trim()
|
|
593
618
|
: historical.name?.trim() || historical.summary?.trim() || historical.runtimeSessionName?.trim() || runtimeSessionId;
|
|
619
|
+
if (isNative && flags["confirm-native-handoff"] !== true && flags.manual !== true) {
|
|
620
|
+
throw new CliError("Native session takeover requires explicit confirmation. Re-run with --confirm-native-handoff (and optionally --observed-at/--history-version from this listing).", "NATIVE_HANDOFF_CONFIRMATION_REQUIRED");
|
|
621
|
+
}
|
|
622
|
+
const observedAtValue = flags["observed-at"];
|
|
623
|
+
const observedAt = observedAtValue === undefined || observedAtValue === true ? Date.now() : Number(observedAtValue);
|
|
624
|
+
if (!Number.isSafeInteger(observedAt) || observedAt <= 0)
|
|
625
|
+
throw new CliError("Invalid --observed-at", "INVALID_ARGUMENT");
|
|
626
|
+
const suppliedIdentity = typeof flags["native-identity"] === "string" ? flags["native-identity"].trim() : historical.nativeIdentity;
|
|
627
|
+
const suppliedHistoryVersion = typeof flags["history-version"] === "string" ? flags["history-version"].trim() : historical.nativeHistoryVersion;
|
|
628
|
+
const nativeIdentity = suppliedIdentity;
|
|
629
|
+
if (isNative && !nativeIdentity)
|
|
630
|
+
throw new CliError("Native session listing did not include a handoff identity; refresh the history listing.", "NATIVE_HANDOFF_PROOF_MISSING");
|
|
631
|
+
if (isNative && historical.nativeIdentity && nativeIdentity !== historical.nativeIdentity)
|
|
632
|
+
throw new CliError("--native-identity does not match the freshly listed native session.", "NATIVE_HANDOFF_RECONFIRM_REQUIRED");
|
|
633
|
+
if (isNative && historical.nativeHistoryVersion && suppliedHistoryVersion !== historical.nativeHistoryVersion)
|
|
634
|
+
throw new CliError("--history-version does not match the freshly listed native session.", "NATIVE_HANDOFF_RECONFIRM_REQUIRED");
|
|
635
|
+
const manualHandoffProof = isNative && nativeIdentity
|
|
636
|
+
? {
|
|
637
|
+
identity: nativeIdentity,
|
|
638
|
+
observedAt,
|
|
639
|
+
...(suppliedHistoryVersion ? { historyVersion: suppliedHistoryVersion } : {}),
|
|
640
|
+
}
|
|
641
|
+
: undefined;
|
|
594
642
|
const continued = await client.importHistoricalSession({
|
|
595
643
|
machineId,
|
|
596
644
|
agent: historical.agent,
|
|
597
645
|
cwd: historical.cwd,
|
|
598
|
-
runtimeSessionId,
|
|
646
|
+
runtimeSessionId: resolvedRuntimeSessionId,
|
|
599
647
|
name,
|
|
600
648
|
...(historical.name ? { providerName: historical.name } : {}),
|
|
601
649
|
...(historical.provider ? { provider: historical.provider } : {}),
|
|
602
650
|
...(historical.providerSessionId ? { providerSessionId: historical.providerSessionId } : {}),
|
|
603
|
-
...(historical.source ? { source: historical.source } : {}),
|
|
651
|
+
...(isNative ? { source: "native" } : historical.source ? { source: historical.source } : {}),
|
|
604
652
|
summary: historical.summary,
|
|
605
653
|
runtimeState: historical.runtimeState,
|
|
606
654
|
...(historical.nativeIdentity ? { nativeIdentity: historical.nativeIdentity } : {}),
|
|
655
|
+
...(manualHandoffProof ? { manualHandoff: true, manualHandoffProof } : {}),
|
|
607
656
|
});
|
|
608
657
|
ok("session.continue", {
|
|
609
658
|
sessionId: continued.sessionId,
|
package/apps/cli/package.json
CHANGED
|
@@ -5,7 +5,7 @@ import { publishRemoteControllerCommand } from "../relay/remote-command.js";
|
|
|
5
5
|
import { claimCommandRequest, sendCommandResponse } from "../relay/send.js";
|
|
6
6
|
import { runtimeTurnSummaryForTask } from "../session/runtime-truth.js";
|
|
7
7
|
import { crewSessionRuntimeState, deliverQueuedCrewAdmission, publishRemoteCrewQueueEnqueue } from "./handler.js";
|
|
8
|
-
import { applyConsumerQueueUpdateToStore, claimCrewQueueHead, completeMaterializedQueueAdmission, failQueuedCrewBacklog, findAdmissionByTaskAndRequest, listPendingCrewQueueNotifications, listPendingConsumerQueueEnqueues, listCrewQueueHeads, markCrewQueueNotificationAttempt, markCrewQueueNotificationApplied, releaseCrewQueueBackoff, recordAdmission, retryClaimedQueueAdmission, runtimeTurnIdForCrewTurn, updatePendingConsumerQueueEnqueue, upsertCrewQueueNotification, upsertSubscription, } from "./store.js";
|
|
8
|
+
import { applyConsumerQueueUpdateToStore, claimCrewQueueHead, completeMaterializedQueueAdmission, failQueuedCrewBacklog, findAdmissionByTaskAndRequest, listPendingCrewQueueNotifications, listPendingConsumerQueueEnqueues, listCrewQueueHeads, markCrewQueueNotificationAttempt, markCrewQueueNotificationApplied, markCrewQueueNotificationDeadLetter, releaseCrewQueueBackoff, recordAdmission, retryClaimedQueueAdmission, runtimeTurnIdForCrewTurn, updatePendingConsumerQueueEnqueue, upsertCrewQueueNotification, upsertSubscription, } from "./store.js";
|
|
9
9
|
import { catchUpSubscriptionEvents } from "./watcher.js";
|
|
10
10
|
const activeDrains = new Map();
|
|
11
11
|
const pendingDrains = new Set();
|
|
@@ -16,6 +16,8 @@ const notificationRetryTimers = new Map();
|
|
|
16
16
|
const consumerEnqueueRetryTimers = new Map();
|
|
17
17
|
let notificationFlushRun;
|
|
18
18
|
let notificationFlushRequested = false;
|
|
19
|
+
let consumerEnqueueRetryRun;
|
|
20
|
+
let consumerEnqueueRetryRequested = false;
|
|
19
21
|
let recoveryRun;
|
|
20
22
|
export async function signalCrewQueue(config, signal) {
|
|
21
23
|
const key = crewQueueKey(signal);
|
|
@@ -242,6 +244,7 @@ export async function handleCrewQueueUpdateCommand(ws, config, command) {
|
|
|
242
244
|
ownerRevision: command.ownerRevision,
|
|
243
245
|
currentRevision: applied.currentRevision,
|
|
244
246
|
applied: applied.applied,
|
|
247
|
+
status: applied.applied ? "applied" : "permanent-rejected",
|
|
245
248
|
});
|
|
246
249
|
if (applied.subscription) {
|
|
247
250
|
void catchUpSubscriptionEvents({ config, subscription: applied.subscription }).catch((error) => {
|
|
@@ -341,6 +344,21 @@ async function completeMaterializedCrewQueueHead(config, queue) {
|
|
|
341
344
|
return terminalRuntimeStatus(summary.status) ? "terminal" : "active";
|
|
342
345
|
}
|
|
343
346
|
async function retryPendingConsumerQueueEnqueues(config) {
|
|
347
|
+
if (consumerEnqueueRetryRun) {
|
|
348
|
+
consumerEnqueueRetryRequested = true;
|
|
349
|
+
return await consumerEnqueueRetryRun;
|
|
350
|
+
}
|
|
351
|
+
consumerEnqueueRetryRun = (async () => {
|
|
352
|
+
do {
|
|
353
|
+
consumerEnqueueRetryRequested = false;
|
|
354
|
+
await retryPendingConsumerQueueEnqueuesOnce(config);
|
|
355
|
+
} while (consumerEnqueueRetryRequested);
|
|
356
|
+
})().finally(() => {
|
|
357
|
+
consumerEnqueueRetryRun = undefined;
|
|
358
|
+
});
|
|
359
|
+
await consumerEnqueueRetryRun;
|
|
360
|
+
}
|
|
361
|
+
async function retryPendingConsumerQueueEnqueuesOnce(config) {
|
|
344
362
|
const pending = await listPendingConsumerQueueEnqueues({ consumerMachineId: config.machineId });
|
|
345
363
|
for (const admission of pending) {
|
|
346
364
|
const key = consumerEnqueueRetryKey(admission);
|
|
@@ -611,6 +629,18 @@ async function flushCrewQueueNotificationsOnce(config) {
|
|
|
611
629
|
expectedType: "machine:crewQueueUpdateApplied",
|
|
612
630
|
timeoutMs: 15_000,
|
|
613
631
|
});
|
|
632
|
+
if (response?.type === "machine:crewQueueUpdateApplied" && response.status === "permanent-rejected") {
|
|
633
|
+
await markCrewQueueNotificationDeadLetter(notification.notificationId);
|
|
634
|
+
await appendAudit({
|
|
635
|
+
machineId: config.machineId,
|
|
636
|
+
sessionId: notification.crewSessionId,
|
|
637
|
+
actor: "system",
|
|
638
|
+
action: "orchestration.queue.consumer_update_dead_letter",
|
|
639
|
+
summary: `Consumer permanently rejected queue update for ${notification.taskId}`,
|
|
640
|
+
evidence: { taskId: notification.taskId, dispatchRequestId: notification.dispatchRequestId, ownerRevision: notification.ownerRevision },
|
|
641
|
+
}).catch(() => undefined);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
614
644
|
if (!crewQueueUpdateAppliedCovers(notification, response)) {
|
|
615
645
|
throw new Error(`Consumer did not apply queue update revision ${notification.ownerRevision} for task ${notification.taskId}`);
|
|
616
646
|
}
|
|
@@ -684,7 +714,8 @@ function crewQueueUpdateAppliedCovers(notification, response) {
|
|
|
684
714
|
response.crewSessionId === notification.crewSessionId &&
|
|
685
715
|
response.ownerRevision === notification.ownerRevision &&
|
|
686
716
|
response.currentRevision >= notification.ownerRevision &&
|
|
687
|
-
response.applied
|
|
717
|
+
response.applied &&
|
|
718
|
+
(response.status === undefined || response.status === "applied" || response.status === "covered");
|
|
688
719
|
}
|
|
689
720
|
function consumerProjectionCoversUpdate(admission, command, currentRevision) {
|
|
690
721
|
if (!admission)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { decryptJson, dispatchRouteCandidates, encryptJson, filterDispatchRouteCandidates, parseMachineClientMessage, randomId } from "../../../../packages/shared/dist/index.js";
|
|
3
3
|
import { appendAudit } from "../audit.js";
|
|
4
|
-
import { sessions, sessionTurns } from "../state.js";
|
|
4
|
+
import { completedCommandResponses, sessions, sessionTurns } from "../state.js";
|
|
5
5
|
import { admitMachineCreateSession, admitMachineFork, admitMachinePrompt } from "../relay/admission.js";
|
|
6
6
|
import { publishRemoteControllerCommand, readRetainedMachineMessage, RemoteCommandError, requestRemoteMachineResult } from "../relay/remote-command.js";
|
|
7
7
|
import { claimCommandRequest, sendCommandResponse } from "../relay/send.js";
|
|
@@ -11,6 +11,11 @@ import { listArtifacts } from "../artifact/store.js";
|
|
|
11
11
|
import { ackAttention, completeClaimedQueueAdmission, crewQueueHasBacklog, deadAttention, failClaimedQueueAdmission, findAdmissionByTask, findAdmissionByTaskAndRequest, latestAttentionForTask, listAdmissions, listAttentions, recordAdmission, recordTurnRuntimeBinding, reopenUnresolvedArtifactFeedbackAttentions, retryAttention, retryClaimedQueueAdmission, runtimeTurnIdForCrewTurn, updateAdmission, updatePendingConsumerQueueEnqueue, upsertSubscription, } from "./store.js";
|
|
12
12
|
import { catchUpSubscriptionEvents } from "./watcher.js";
|
|
13
13
|
export async function handleDispatchCommand(ws, config, command) {
|
|
14
|
+
// Status/list are read projections, not mutation retries. A caller may use
|
|
15
|
+
// the same correlation id while the durable admission changes; never replay
|
|
16
|
+
// the 24h mutation response cache for these reads.
|
|
17
|
+
if (command.action === "status" || command.action === "list")
|
|
18
|
+
completedCommandResponses.delete(command.requestId);
|
|
14
19
|
if (!claimCommandRequest(ws, command.requestId))
|
|
15
20
|
return;
|
|
16
21
|
if (command.machineId !== config.machineId) {
|
|
@@ -597,7 +602,12 @@ function validateRouteDecisionCandidate(decision, candidates) {
|
|
|
597
602
|
};
|
|
598
603
|
}
|
|
599
604
|
if (candidate.availability !== "idle") {
|
|
600
|
-
|
|
605
|
+
// An explicit Queue Reuse decision is a durable admission strategy. A
|
|
606
|
+
// remote runtime snapshot may be temporarily unknown while the owner is
|
|
607
|
+
// reconnecting; do not turn that observation into a route rejection or
|
|
608
|
+
// pretend it is idle. The owner-side enqueue will re-check runtime truth
|
|
609
|
+
// and either retain the inbox item or fail a permanently unavailable Crew.
|
|
610
|
+
if (decision.kind === "reuse" && decision.ifBusy === "queue" && (candidate.availability === "busy" || candidate.availability === "unknown"))
|
|
601
611
|
return undefined;
|
|
602
612
|
if (decision.kind === "fork" && candidate.availability === "unknown") {
|
|
603
613
|
return {
|
|
@@ -153,6 +153,7 @@ export declare function markCrewQueueNotificationApplied(input: {
|
|
|
153
153
|
ownerRevision: number;
|
|
154
154
|
currentRevision: number;
|
|
155
155
|
}): Promise<boolean>;
|
|
156
|
+
export declare function markCrewQueueNotificationDeadLetter(notificationId: string): Promise<boolean>;
|
|
156
157
|
export declare function markCrewQueueNotificationAttempt(input: {
|
|
157
158
|
notificationId: string;
|
|
158
159
|
error: string;
|
|
@@ -166,6 +166,18 @@ export async function markCrewQueueNotificationApplied(input) {
|
|
|
166
166
|
});
|
|
167
167
|
return removed;
|
|
168
168
|
}
|
|
169
|
+
export async function markCrewQueueNotificationDeadLetter(notificationId) {
|
|
170
|
+
let removed = false;
|
|
171
|
+
await updateOrchestrationStore((current) => {
|
|
172
|
+
const index = current.queueNotifications.findIndex((record) => record.notificationId === notificationId);
|
|
173
|
+
if (index < 0)
|
|
174
|
+
return current;
|
|
175
|
+
current.queueNotifications.splice(index, 1);
|
|
176
|
+
removed = true;
|
|
177
|
+
return current;
|
|
178
|
+
});
|
|
179
|
+
return removed;
|
|
180
|
+
}
|
|
169
181
|
export async function markCrewQueueNotificationAttempt(input) {
|
|
170
182
|
const now = new Date();
|
|
171
183
|
const updatedAt = now.toISOString();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { decryptJson, parseControllerClientMessage, parseInternalControllerMessage, parseRelayServerWireMessage, randomId, } from "../../../../packages/shared/dist/index.js";
|
|
1
|
+
import { decryptJson, parseControllerClientMessage, parseInternalControllerMessage, parseMachineClientMessage, parseRelayServerWireMessage, randomId, } from "../../../../packages/shared/dist/index.js";
|
|
2
2
|
import { appendAudit } from "../audit.js";
|
|
3
3
|
import { drainPendingArtifactFeedbackAttentions, handleArtifactCommand } from "../artifact/handler.js";
|
|
4
4
|
import { handleGatewayCommand } from "../gateway/handler.js";
|
|
@@ -21,11 +21,42 @@ import { nativeImportSessionId } from "../session/native-identity.js";
|
|
|
21
21
|
import { encryptedSessionMetadata } from "../session/metadata.js";
|
|
22
22
|
import { loadSessionProjections } from "../session/store.js";
|
|
23
23
|
import { relayWsUrl } from "../relay-http.js";
|
|
24
|
-
import { sessions, sessionTurns } from "../state.js";
|
|
24
|
+
import { applyRelayMachinePresence, applyRelayMachineSnapshot, reconcileRelayMachinePresence, relayMachineRegistry, sessions, sessionTurns } from "../state.js";
|
|
25
25
|
import { handleDevExec, handleDiagnose } from "./devtools.js";
|
|
26
26
|
import { handleInboundPromptCommand, recoverInboundPromptAdmissions } from "./inbound-prompt.js";
|
|
27
27
|
import { registerMachine } from "./register.js";
|
|
28
28
|
import { clearActiveRelaySocket, configureRelaySender, flushRelayOutbox, handleMachineMessageAck, handleRetainedPacketAck, handleRetainedPacketError, sendCommandResponse, setActiveRelaySocket, } from "./send.js";
|
|
29
|
+
async function applyMachineManifest(config, retained, presence) {
|
|
30
|
+
try {
|
|
31
|
+
const message = parseMachineClientMessage(await decryptJson(config.accountSecret, retained.encryptedPayload));
|
|
32
|
+
if (message.type !== "machine:register")
|
|
33
|
+
return;
|
|
34
|
+
let name = retained.updatedByNodeId;
|
|
35
|
+
if (message.encryptedMetadata) {
|
|
36
|
+
const metadata = await decryptJson(config.accountSecret, message.encryptedMetadata);
|
|
37
|
+
if (typeof metadata.name === "string" && metadata.name.trim())
|
|
38
|
+
name = metadata.name.trim();
|
|
39
|
+
else if (typeof metadata.hostname === "string" && metadata.hostname.trim())
|
|
40
|
+
name = metadata.hostname.trim();
|
|
41
|
+
}
|
|
42
|
+
const previous = relayMachineRegistry.get(retained.updatedByNodeId);
|
|
43
|
+
applyRelayMachineSnapshot({
|
|
44
|
+
id: retained.updatedByNodeId,
|
|
45
|
+
name,
|
|
46
|
+
// A retained manifest describes identity/capabilities only. Preserve
|
|
47
|
+
// the authoritative presence projection when no live presence record
|
|
48
|
+
// accompanied it; never infer online from a static retained packet.
|
|
49
|
+
online: presence?.online ?? previous?.online ?? false,
|
|
50
|
+
lastSeen: presence?.lastSeen ?? previous?.lastSeen ?? retained.updatedAt,
|
|
51
|
+
capabilities: message.capabilities,
|
|
52
|
+
encryptedMetadata: message.encryptedMetadata,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Retained manifests are encrypted account state; malformed/unrelated
|
|
57
|
+
// packets must not prevent discovery of other machines.
|
|
58
|
+
}
|
|
59
|
+
}
|
|
29
60
|
export async function handleServerMessage(ws, config, message) {
|
|
30
61
|
if (message.type === "server:hello") {
|
|
31
62
|
await registerMachine(ws, config);
|
|
@@ -186,6 +217,34 @@ async function handleRelayWireMessage(ws, config, message) {
|
|
|
186
217
|
});
|
|
187
218
|
return;
|
|
188
219
|
}
|
|
220
|
+
if (message.type === "server:snapshot") {
|
|
221
|
+
for (const presence of message.presence)
|
|
222
|
+
applyRelayMachinePresence(presence);
|
|
223
|
+
reconcileRelayMachinePresence(message.presence);
|
|
224
|
+
for (const presence of message.presence) {
|
|
225
|
+
if (presence.manifest)
|
|
226
|
+
await applyMachineManifest(config, presence.manifest, presence);
|
|
227
|
+
}
|
|
228
|
+
for (const retained of message.retainedPackets) {
|
|
229
|
+
try {
|
|
230
|
+
const retainedMessage = parseMachineClientMessage(await decryptJson(config.accountSecret, retained.encryptedPayload));
|
|
231
|
+
if (retainedMessage.type === "machine:register") {
|
|
232
|
+
await applyMachineManifest(config, retained);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
// Retained packets are encrypted per account; an unrelated/invalid
|
|
237
|
+
// retained entry must not prevent presence discovery for other nodes.
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (message.type === "server:presence") {
|
|
243
|
+
applyRelayMachinePresence(message.presence);
|
|
244
|
+
if (message.presence.manifest)
|
|
245
|
+
await applyMachineManifest(config, message.presence.manifest, message.presence);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
189
248
|
if (message.type === "server:ack") {
|
|
190
249
|
if (message.packetId)
|
|
191
250
|
await handleMachineMessageAck(message.packetId);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { createHeadlessRuntimeClient, createHostedHeadlessRuntimeClient, ensureHeadlessRuntimeHost, resolveHeadlessRuntimeHostPaths, } from "../../../../packages/headless-runtime/dist/index.js";
|
|
@@ -44,7 +45,7 @@ export class HeadlessRuntimeManager {
|
|
|
44
45
|
async invalidateClientForBinding(binding, cwd = this.options.defaultCwd) {
|
|
45
46
|
if (!isDaemonHeadlessProvider(binding.provider))
|
|
46
47
|
return;
|
|
47
|
-
const resolvedCwd =
|
|
48
|
+
const resolvedCwd = canonicalWorkspacePath(cwd);
|
|
48
49
|
const hostOptions = this.hostOptions(binding.provider, resolvedCwd, binding.profile);
|
|
49
50
|
const hostPaths = resolveHeadlessRuntimeHostPaths(hostOptions);
|
|
50
51
|
const key = `${binding.provider}\0${hostPaths.profile}\0${resolvedCwd}`;
|
|
@@ -66,7 +67,7 @@ export class HeadlessRuntimeManager {
|
|
|
66
67
|
if (!this.enabledProviders().includes(normalizedProvider)) {
|
|
67
68
|
throw providerConfigError(`Headless Runtime provider ${normalizedProvider} is not enabled.`, "PROVIDER_NOT_ENABLED");
|
|
68
69
|
}
|
|
69
|
-
const resolvedCwd =
|
|
70
|
+
const resolvedCwd = canonicalWorkspacePath(cwd);
|
|
70
71
|
const hostOptions = this.hostOptions(normalizedProvider, resolvedCwd, profileOverride);
|
|
71
72
|
const hostPaths = resolveHeadlessRuntimeHostPaths(hostOptions);
|
|
72
73
|
const key = `${normalizedProvider}\0${hostPaths.profile}\0${resolvedCwd}`;
|
|
@@ -168,7 +169,7 @@ export class HeadlessRuntimeManager {
|
|
|
168
169
|
const entry = await this.clientForProvider(provider, cwd);
|
|
169
170
|
const catalog = await entry.client.listModels({ provider: entry.runtimeProvider, cwd }).catch(() => undefined);
|
|
170
171
|
const base = baseProviderProfile(provider);
|
|
171
|
-
const
|
|
172
|
+
const catalogModels = catalog?.models
|
|
172
173
|
?.map((model) => ({
|
|
173
174
|
id: model.id,
|
|
174
175
|
...(model.label ? { label: model.label } : {}),
|
|
@@ -186,6 +187,13 @@ export class HeadlessRuntimeManager {
|
|
|
186
187
|
: {}),
|
|
187
188
|
...(model.defaultThinkingLevel ? { defaultThinkingLevel: model.defaultThinkingLevel } : {}),
|
|
188
189
|
})) ?? [];
|
|
190
|
+
// Some provider/app-server versions expose the effective default only as a
|
|
191
|
+
// scalar and omit it from the selectable array. Keep the actual default
|
|
192
|
+
// visible to Web/CLI without inventing a model name (for example a newer
|
|
193
|
+
// Astra build on a machine whose catalog is older).
|
|
194
|
+
const models = catalog?.defaultModel && !catalogModels.some((model) => model.id === catalog.defaultModel)
|
|
195
|
+
? [...catalogModels, { id: catalog.defaultModel }]
|
|
196
|
+
: catalogModels;
|
|
189
197
|
const thinkingLevels = catalog?.thinkingLevels
|
|
190
198
|
?.map((level) => ({
|
|
191
199
|
id: level.id,
|
|
@@ -236,7 +244,34 @@ export class HeadlessRuntimeManager {
|
|
|
236
244
|
}
|
|
237
245
|
}
|
|
238
246
|
function hashPath(input) {
|
|
239
|
-
return createHash("sha256").update(
|
|
247
|
+
return createHash("sha256").update(canonicalWorkspacePath(input)).digest("hex").slice(0, 16);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Workspace identity must be stable across filesystem aliases. On macOS
|
|
251
|
+
* `/private/tmp` and `/tmp` are the same directory; on Windows realpath also
|
|
252
|
+
* normalizes drive and reparse-point aliases. Keep the absolute fallback for
|
|
253
|
+
* a not-yet-created leaf, while canonicalizing the existing parent.
|
|
254
|
+
*/
|
|
255
|
+
function canonicalWorkspacePath(input) {
|
|
256
|
+
const resolved = path.resolve(input);
|
|
257
|
+
try {
|
|
258
|
+
return realpathSync.native(resolved);
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
let current = resolved;
|
|
262
|
+
const suffix = [];
|
|
263
|
+
while (current !== path.dirname(current)) {
|
|
264
|
+
try {
|
|
265
|
+
const canonical = realpathSync.native(current);
|
|
266
|
+
return path.join(canonical, ...suffix.reverse());
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
suffix.push(path.basename(current));
|
|
270
|
+
current = path.dirname(current);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return resolved;
|
|
274
|
+
}
|
|
240
275
|
}
|
|
241
276
|
function headlessRuntimeProviderForDaemon(provider) {
|
|
242
277
|
return provider === "pie" ? "command" : provider;
|
|
@@ -37,8 +37,19 @@ export async function listHeadlessHistoricalSessions(input) {
|
|
|
37
37
|
const cursorBuffer = effectiveCursor?.buffer
|
|
38
38
|
? await boundedCursorBuffer(effectiveCursor.buffer, cwd, input.agent, input.includeArchived, providers)
|
|
39
39
|
: [];
|
|
40
|
-
if (providers.length === 0)
|
|
40
|
+
if (providers.length === 0) {
|
|
41
|
+
if (input.agent) {
|
|
42
|
+
return {
|
|
43
|
+
sessions: [],
|
|
44
|
+
warnings: [{
|
|
45
|
+
provider: input.agent,
|
|
46
|
+
code: "unconfigured",
|
|
47
|
+
message: `Provider ${input.agent} is not enabled on this daemon; no native history was queried.`,
|
|
48
|
+
}],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
41
51
|
return { sessions: [] };
|
|
52
|
+
}
|
|
42
53
|
const states = new Map();
|
|
43
54
|
const warnings = [];
|
|
44
55
|
for (const provider of providers) {
|
|
@@ -230,6 +241,12 @@ function historicalSessionFromThread(thread, provider) {
|
|
|
230
241
|
updatedAt: thread.updatedAt,
|
|
231
242
|
runtimeState: thread.archived ? "archived" : "available",
|
|
232
243
|
...(thread.name ? { name: thread.name, runtimeSessionName: thread.name } : { runtimeSessionName: thread.id }),
|
|
244
|
+
...(typeof thread.providerMetadata?.nativeIdentity === "string" && thread.providerMetadata.nativeIdentity.trim()
|
|
245
|
+
? { nativeIdentity: thread.providerMetadata.nativeIdentity.trim() }
|
|
246
|
+
: {}),
|
|
247
|
+
...(typeof thread.providerMetadata?.nativeHistoryVersion === "string" && thread.providerMetadata.nativeHistoryVersion.trim()
|
|
248
|
+
? { nativeHistoryVersion: thread.providerMetadata.nativeHistoryVersion.trim() }
|
|
249
|
+
: {}),
|
|
233
250
|
};
|
|
234
251
|
}
|
|
235
252
|
function nativeHistoricalSessionFromSummary(session, provider, profile, nativeDataRootFingerprint, machineId) {
|
|
@@ -446,6 +463,8 @@ function bufferedSessionFromUnknown(value) {
|
|
|
446
463
|
...(typeof record.runtimeSessionName === "string" ? { runtimeSessionName: record.runtimeSessionName } : {}),
|
|
447
464
|
...(record.historyStatus === "available" || record.historyStatus === "unsupported" || record.historyStatus === "unconfigured" || record.historyStatus === "permission-denied" ? { historyStatus: record.historyStatus } : {}),
|
|
448
465
|
...(typeof record.capabilityMessage === "string" ? { capabilityMessage: record.capabilityMessage } : {}),
|
|
466
|
+
...(typeof record.nativeIdentity === "string" ? { nativeIdentity: record.nativeIdentity } : {}),
|
|
467
|
+
...(typeof record.nativeHistoryVersion === "string" ? { nativeHistoryVersion: record.nativeHistoryVersion } : {}),
|
|
449
468
|
};
|
|
450
469
|
}
|
|
451
470
|
async function boundedCursorBuffer(buffer, cwd, requestedAgent, includeArchived, providers) {
|
|
@@ -195,7 +195,7 @@ export async function artifactList(input) {
|
|
|
195
195
|
...(stringArg(input.args, "taskId") ? { taskId: stringArg(input.args, "taskId") } : {}),
|
|
196
196
|
};
|
|
197
197
|
const message = machineId === input.config.machineId
|
|
198
|
-
? await runMachineHandler(input.config, requestId, async (ws) => await handleArtifactCommand(ws, input.config, { ...command, type: "machine:artifact" }))
|
|
198
|
+
? await runMachineHandler(input.config, requestId, async (ws) => await handleArtifactCommand(ws, input.config, { ...command, type: "machine:artifact" }), { cacheResponse: false })
|
|
199
199
|
: await requestRemoteMachineResult(input.config, { targetMachineId: machineId, expectedType: "machine:artifactResult", command, timeoutMs: 20_000 }).catch((error) => ({
|
|
200
200
|
type: "machine:error",
|
|
201
201
|
requestId,
|
|
@@ -211,8 +211,9 @@ export async function artifactStatus(input) {
|
|
|
211
211
|
const artifactId = stringArg(input.args, "artifactId");
|
|
212
212
|
if (!artifactId)
|
|
213
213
|
return missingArg("artifact.status", "artifactId");
|
|
214
|
-
const
|
|
215
|
-
const
|
|
214
|
+
const requestedProducerMachineId = stringArg(input.args, "producerMachineId") ?? stringArg(input.args, "machineId");
|
|
215
|
+
const localProjection = await localArtifactStatus({ artifactId, ...(requestedProducerMachineId ? { producerMachineId: requestedProducerMachineId } : {}) });
|
|
216
|
+
const producerMachineId = requestedProducerMachineId ?? localProjection?.producerMachineId ?? input.config.machineId;
|
|
216
217
|
const requestId = readArtifactRequestId("artifact.status", input.args);
|
|
217
218
|
const command = {
|
|
218
219
|
type: "controller:artifact",
|
|
@@ -223,7 +224,7 @@ export async function artifactStatus(input) {
|
|
|
223
224
|
artifactId,
|
|
224
225
|
};
|
|
225
226
|
const message = producerMachineId === input.config.machineId
|
|
226
|
-
? await runMachineHandler(input.config, requestId, async (ws) => await handleArtifactCommand(ws, input.config, { ...command, type: "machine:artifact" }))
|
|
227
|
+
? await runMachineHandler(input.config, requestId, async (ws) => await handleArtifactCommand(ws, input.config, { ...command, type: "machine:artifact" }), { cacheResponse: false })
|
|
227
228
|
: await requestRemoteMachineResult(input.config, { targetMachineId: producerMachineId, expectedType: "machine:artifactResult", command, timeoutMs: 20_000 }).catch((error) => ({
|
|
228
229
|
type: "machine:error",
|
|
229
230
|
requestId,
|
|
@@ -239,8 +240,9 @@ export async function artifactFeedbackList(input) {
|
|
|
239
240
|
const artifactId = stringArg(input.args, "artifactId");
|
|
240
241
|
if (!artifactId)
|
|
241
242
|
return missingArg("artifact.feedback.list", "artifactId");
|
|
242
|
-
const
|
|
243
|
-
const
|
|
243
|
+
const requestedProducerMachineId = stringArg(input.args, "producerMachineId") ?? stringArg(input.args, "machineId");
|
|
244
|
+
const localProjection = await localArtifactStatus({ artifactId, ...(requestedProducerMachineId ? { producerMachineId: requestedProducerMachineId } : {}) });
|
|
245
|
+
const producerMachineId = requestedProducerMachineId ?? localProjection?.producerMachineId ?? input.config.machineId;
|
|
244
246
|
const requestId = readArtifactRequestId("artifact.feedback.list", input.args);
|
|
245
247
|
const command = {
|
|
246
248
|
type: "controller:artifact",
|
|
@@ -252,7 +254,7 @@ export async function artifactFeedbackList(input) {
|
|
|
252
254
|
...(stringArg(input.args, "after") ? { after: stringArg(input.args, "after") } : {}),
|
|
253
255
|
};
|
|
254
256
|
const message = producerMachineId === input.config.machineId
|
|
255
|
-
? await runMachineHandler(input.config, requestId, async (ws) => await handleArtifactCommand(ws, input.config, { ...command, type: "machine:artifact" }))
|
|
257
|
+
? await runMachineHandler(input.config, requestId, async (ws) => await handleArtifactCommand(ws, input.config, { ...command, type: "machine:artifact" }), { cacheResponse: false })
|
|
256
258
|
: await requestRemoteMachineResult(input.config, { targetMachineId: producerMachineId, expectedType: "machine:artifactResult", command, timeoutMs: 20_000 }).catch((error) => ({
|
|
257
259
|
type: "machine:error",
|
|
258
260
|
requestId,
|