pi-subagents 0.45.1 → 0.45.2
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/CHANGELOG.md +9 -0
- package/package.json +1 -1
- package/src/runs/background/resume-guidance.ts +33 -0
- package/src/runs/background/subagent-wait.ts +5 -2
- package/src/runs/background/wait-subscriptions.ts +4 -3
- package/src/runs/foreground/subagent-executor.ts +22 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +71 -5
- package/src/workflows/scripted-workflow.ts +20 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.45.2] - 2026-08-10
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- Tell parents to revive resumable failed async runs before reporting failure or launching a replacement. Thanks to @Livan-pro for #938.
|
|
9
|
+
- Persist the actual agent and session file for workflow children when they start so their sessions can resume after a parent restart. Thanks to @Livan-pro for #932.
|
|
10
|
+
- Retry steering requests that remain pending after manual compaction and fail unresolved requests at shutdown. Thanks to @jtac for #933.
|
|
11
|
+
- Omit undefined object fields from `workflowScript` return values so completed `runs.all` results are not discarded when callers include unsupported fields such as `status` (#930).
|
|
12
|
+
- Keep child steering inbox `auto` requests queued between `agent_end` and `agent_settled` so settlement-time guidance is not sent as an idle prompt too early. Thanks to @jtac for #928.
|
|
13
|
+
|
|
5
14
|
## [0.45.1] - 2026-08-09
|
|
6
15
|
|
|
7
16
|
### Changed
|
package/package.json
CHANGED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import type { AsyncRunSummary } from "./async-status.ts";
|
|
3
|
+
|
|
4
|
+
export function formatAsyncReviveCommand(run: AsyncRunSummary): string | undefined {
|
|
5
|
+
const step = run.steps.find((candidate) => candidate.status === "failed" && candidate.sessionFile && fs.existsSync(candidate.sessionFile));
|
|
6
|
+
if (!step) {
|
|
7
|
+
if (run.steps.length === 1 && run.sessionFile && fs.existsSync(run.sessionFile)) {
|
|
8
|
+
return `subagent({ action: "resume", id: "${run.id}", message: "Continue from the persisted child session and report the result." })`;
|
|
9
|
+
}
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
const index = run.steps.length === 1 ? "" : `, index: ${step.index}`;
|
|
13
|
+
return `subagent({ action: "resume", id: "${run.id}"${index}, message: "Continue from the persisted child session and report the result." })`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function formatResumeFirstFailedRunDetail(run: AsyncRunSummary): string | undefined {
|
|
17
|
+
if (run.state !== "failed") return undefined;
|
|
18
|
+
const command = formatAsyncReviveCommand(run);
|
|
19
|
+
if (!command) return undefined;
|
|
20
|
+
return `Resume-first: failed run "${run.id}" has a persisted child session. Revive the original run with ${command} before reporting failure or launching a replacement. Launch a replacement only if revive fails or the user explicitly asks for one.`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function formatResumeFirstFailedRunsNote(runs: AsyncRunSummary[]): string {
|
|
24
|
+
const resumable = runs
|
|
25
|
+
.filter((run) => run.state === "failed")
|
|
26
|
+
.map((run) => ({ run, command: formatAsyncReviveCommand(run) }))
|
|
27
|
+
.filter((entry): entry is { run: AsyncRunSummary; command: string } => Boolean(entry.command));
|
|
28
|
+
if (resumable.length === 0) return "";
|
|
29
|
+
const guidance = resumable.length === 1
|
|
30
|
+
? `failed run "${resumable[0]!.run.id}" has a persisted child session. Revive the original run with ${resumable[0]!.command}`
|
|
31
|
+
: `${resumable.length} failed runs have persisted child sessions. Inspect status and revive each original run before retrying`;
|
|
32
|
+
return ` Resume-first: ${guidance} before reporting failure or launching a replacement. Launch a replacement only if revive fails or the user explicitly asks for one.`;
|
|
33
|
+
}
|
|
@@ -62,6 +62,7 @@ import {
|
|
|
62
62
|
} from "../../shared/types.ts";
|
|
63
63
|
import { formatDuration, shortenPath } from "../../shared/formatters.ts";
|
|
64
64
|
import { collectWaitCompletions } from "./wait-completions.ts";
|
|
65
|
+
import { formatResumeFirstFailedRunsNote } from "./resume-guidance.ts";
|
|
65
66
|
export { WAIT_TOOL_ENABLED_ENV, resolveWaitToolConfig, type ResolvedWaitToolConfig } from "./wait-config.ts";
|
|
66
67
|
|
|
67
68
|
/** States that mean a run is still in flight (not yet resolved). */
|
|
@@ -604,6 +605,7 @@ export async function waitForSubagents(
|
|
|
604
605
|
let finishedAsyncCount: number;
|
|
605
606
|
let failedAsyncCount: number;
|
|
606
607
|
let completions: WaitCompletion[] | undefined;
|
|
608
|
+
let resumeGuidance = "";
|
|
607
609
|
const activeProviderIds = new Set(providerActive.map(backgroundWorkIdentity));
|
|
608
610
|
const providerFinishedCount = [...initialProviderIds].filter((id) => !activeProviderIds.has(id)).length;
|
|
609
611
|
try {
|
|
@@ -612,6 +614,7 @@ export async function waitForSubagents(
|
|
|
612
614
|
finishedAsyncCount = terminal.length;
|
|
613
615
|
failedAsyncCount = terminal.filter((run) => run.state === "failed").length;
|
|
614
616
|
terminalSummary = summarizeTerminalRuns(terminal, providerFinishedCount);
|
|
617
|
+
resumeGuidance = formatResumeFirstFailedRunsNote(terminal);
|
|
615
618
|
completions = collectWaitCompletions(terminal, deps.state, deps.resultsDir ?? DIRS.results);
|
|
616
619
|
} catch (error) {
|
|
617
620
|
return result(error instanceof Error ? error.message : String(error), true);
|
|
@@ -637,7 +640,7 @@ export async function waitForSubagents(
|
|
|
637
640
|
: `${initialAsyncIds.size} async run(s) and ${initialProviderIds.size} provider item(s)`;
|
|
638
641
|
const status = relevantAttention.length > 0 ? "attention required" : "done";
|
|
639
642
|
return result(
|
|
640
|
-
`Waited ${elapsed} for ${scope}; ${status}.${outcome}${attentionNote} Completion/control events have been observed; inspect status if a notification is not visible yet.`,
|
|
643
|
+
`Waited ${elapsed} for ${scope}; ${status}.${outcome}${resumeGuidance}${attentionNote} Completion/control events have been observed; inspect status if a notification is not visible yet.`,
|
|
641
644
|
deps.failOnFailedRuns === true && failedAsyncCount > 0,
|
|
642
645
|
completions,
|
|
643
646
|
);
|
|
@@ -654,7 +657,7 @@ export async function waitForSubagents(
|
|
|
654
657
|
? `${relevantAttention.length} of ${initialCount} ${subject} need attention`
|
|
655
658
|
: `${finishedCount} of ${initialCount} ${subject} finished`;
|
|
656
659
|
return result(
|
|
657
|
-
`Waited ${elapsed}; ${progress}.${outcome}${attentionNote}${remainder} Relevant completion/control events have been observed; inspect status if a notification is not visible yet.`,
|
|
660
|
+
`Waited ${elapsed}; ${progress}.${outcome}${resumeGuidance}${attentionNote}${remainder} Relevant completion/control events have been observed; inspect status if a notification is not visible yet.`,
|
|
658
661
|
deps.failOnFailedRuns === true && failedAsyncCount > 0,
|
|
659
662
|
completions,
|
|
660
663
|
);
|
|
@@ -2,7 +2,8 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
5
|
-
import { listAsyncRuns } from "./async-status.ts";
|
|
5
|
+
import { listAsyncRuns, type AsyncRunSummary } from "./async-status.ts";
|
|
6
|
+
import { formatResumeFirstFailedRunDetail } from "./resume-guidance.ts";
|
|
6
7
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
7
8
|
import {
|
|
8
9
|
DIRS,
|
|
@@ -61,7 +62,7 @@ function parseRecord(value: unknown): WaitSubscriptionRecord | undefined {
|
|
|
61
62
|
return record as WaitSubscriptionRecord;
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
function needsAttention(run:
|
|
65
|
+
function needsAttention(run: AsyncRunSummary): boolean {
|
|
65
66
|
return run.activityState === "needs_attention" || run.steps.some((step) => step.activityState === "needs_attention");
|
|
66
67
|
}
|
|
67
68
|
|
|
@@ -166,7 +167,7 @@ export function createWaitSubscriptionManager(
|
|
|
166
167
|
return;
|
|
167
168
|
}
|
|
168
169
|
if (run.state !== "queued" && run.state !== "running") {
|
|
169
|
-
settle(record, run.state === "complete" ? "completed" : run.state, "Inspect the run status for its final output.");
|
|
170
|
+
settle(record, run.state === "complete" ? "completed" : run.state, formatResumeFirstFailedRunDetail(run) ?? "Inspect the run status for its final output.");
|
|
170
171
|
}
|
|
171
172
|
};
|
|
172
173
|
|
|
@@ -3942,6 +3942,8 @@ function duplicateSubagentCallResult(params: SubagentParamsLike): AgentToolResul
|
|
|
3942
3942
|
};
|
|
3943
3943
|
}
|
|
3944
3944
|
|
|
3945
|
+
const workflowLaunchObservers = new WeakMap<object, (launch: { agent: string; sessionFile?: string }) => void>();
|
|
3946
|
+
|
|
3945
3947
|
function workflowChildResult(key: string, result: AgentToolResult<Details>): WorkflowScriptChildResult {
|
|
3946
3948
|
const receiptOutput = result.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n");
|
|
3947
3949
|
const output = result.details.results.length === 1 && result.details.results[0]?.finalOutput !== undefined
|
|
@@ -4159,6 +4161,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4159
4161
|
ctx: ExtensionContext,
|
|
4160
4162
|
preserveActiveSession = false,
|
|
4161
4163
|
): Promise<AgentToolResult<Details>> => {
|
|
4164
|
+
const workflowLaunchObserver = workflowLaunchObservers.get(params);
|
|
4162
4165
|
const delegatedThinkingOverride = delegatedThinkingOverrides.get(params);
|
|
4163
4166
|
const allowZeroToolBudget = delegatedZeroToolBudgets.has(params);
|
|
4164
4167
|
if (!preserveActiveSession) deps.state.baseCwd = ctx.cwd;
|
|
@@ -4349,6 +4352,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4349
4352
|
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
|
|
4350
4353
|
patchMissionObjective(childParams.task);
|
|
4351
4354
|
const childRequest = prepareWorkflowLaunchParams(workflowChildDefaults, childParams, workflowRunId, key, { missionDetached: detachWorkflowChildMissions });
|
|
4355
|
+
workflowLaunchObservers.set(childRequest, (launch) => {
|
|
4356
|
+
const step = status.steps?.find((candidate) => candidate.workflowKey === key);
|
|
4357
|
+
if (!step) return;
|
|
4358
|
+
step.agent = launch.agent;
|
|
4359
|
+
step.sessionFile = launch.sessionFile;
|
|
4360
|
+
persist();
|
|
4361
|
+
});
|
|
4352
4362
|
const result = await execute(randomUUID(), childRequest, workflowSignal, (update) => {
|
|
4353
4363
|
const progress = update.details.progress?.[0];
|
|
4354
4364
|
const step = status.steps?.find((candidate) => candidate.workflowKey === key);
|
|
@@ -5421,6 +5431,18 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5421
5431
|
|
|
5422
5432
|
let nestedForegroundStarted = false;
|
|
5423
5433
|
try {
|
|
5434
|
+
if (workflowLaunchObserver) {
|
|
5435
|
+
const singleTask = hasTasks && effectiveParams.tasks?.length === 1 ? effectiveParams.tasks[0] : undefined;
|
|
5436
|
+
const launch = hasSingle
|
|
5437
|
+
? { agent: effectiveParams.agent!, sessionFile: childSessionFileForTask(effectiveParams.agent!, 0, effectiveParams.model) }
|
|
5438
|
+
: singleTask
|
|
5439
|
+
? { agent: singleTask.agent, sessionFile: childSessionFileForTask(singleTask.agent, 0, singleTask.model) }
|
|
5440
|
+
: undefined;
|
|
5441
|
+
if (launch) {
|
|
5442
|
+
workflowLaunchObservers.delete(params);
|
|
5443
|
+
workflowLaunchObserver(launch);
|
|
5444
|
+
}
|
|
5445
|
+
}
|
|
5424
5446
|
const asyncResult = runAsyncPath(execData, deps);
|
|
5425
5447
|
if (asyncResult) return attachMission(withResolvedContext(withForkThinkingNotes(asyncResult, forkThinkingDowngrades), contextPolicy.contextSummary));
|
|
5426
5448
|
if (foregroundControl) {
|
|
@@ -30,6 +30,7 @@ import { drainOutstandingWork } from "../background/auto-drain.ts";
|
|
|
30
30
|
const SUBAGENT_INHERIT_PROJECT_CONTEXT_ENV = "PI_SUBAGENT_INHERIT_PROJECT_CONTEXT";
|
|
31
31
|
const SUBAGENT_INHERIT_SKILLS_ENV = "PI_SUBAGENT_INHERIT_SKILLS";
|
|
32
32
|
export const SUBAGENT_INTERCOM_SESSION_NAME_ENV = "PI_SUBAGENT_INTERCOM_SESSION_NAME";
|
|
33
|
+
const STEERING_LEGACY_SETTLE_FALLBACK_MS = 1000;
|
|
33
34
|
|
|
34
35
|
const STRUCTURED_OUTPUT_INSTRUCTIONS = [
|
|
35
36
|
"This subagent step has a strict structured output contract.",
|
|
@@ -330,7 +331,7 @@ function registerToolBudget(pi: ExtensionAPI, budget: ResolvedToolBudget | undef
|
|
|
330
331
|
|
|
331
332
|
export function registerSteeringInbox(
|
|
332
333
|
pi: ExtensionAPI,
|
|
333
|
-
deps: { watch?: typeof fs.watch; nativeRealpath?: (filePath: string) => string } = {},
|
|
334
|
+
deps: { watch?: typeof fs.watch; nativeRealpath?: (filePath: string) => string; legacySettleFallbackMs?: number } = {},
|
|
334
335
|
): void {
|
|
335
336
|
const steerInbox = process.env[SUBAGENT_STEER_INBOX_ENV]?.trim();
|
|
336
337
|
if (!steerInbox) return;
|
|
@@ -343,11 +344,14 @@ export function registerSteeringInbox(
|
|
|
343
344
|
let disposed = false;
|
|
344
345
|
let agentRunning = false;
|
|
345
346
|
let inTurn = false;
|
|
347
|
+
let awaitingSettlement = false;
|
|
346
348
|
let flushing = false;
|
|
347
349
|
let started = false;
|
|
348
350
|
let canSteer = typeof sendUserMessage === "function";
|
|
349
351
|
let watcher: fs.FSWatcher | undefined;
|
|
350
352
|
let interval: NodeJS.Timeout | undefined;
|
|
353
|
+
let settleFallback: NodeJS.Timeout | undefined;
|
|
354
|
+
const legacySettleFallbackMs = deps.legacySettleFallbackMs ?? STEERING_LEGACY_SETTLE_FALLBACK_MS;
|
|
351
355
|
const acknowledge = (request: SteerRequest, state: "delivered" | "queued" | "failed", message: string, deliveryStatus?: SteerDeliveryStatus): void => {
|
|
352
356
|
if (!ackDir || !Number.isInteger(childIndex) || childIndex < 0) return;
|
|
353
357
|
writeSteerAckAt(steerAckPathFromDir(ackDir, request.id), {
|
|
@@ -375,7 +379,8 @@ export function registerSteeringInbox(
|
|
|
375
379
|
continue;
|
|
376
380
|
}
|
|
377
381
|
const requestedMode = request.mode ?? "steer";
|
|
378
|
-
const
|
|
382
|
+
const autoCanUseIdle = requestedMode === "auto" && !agentRunning && !awaitingSettlement;
|
|
383
|
+
const delivery = requestedMode === "follow_up" || (requestedMode === "auto" && (inTurn || awaitingSettlement)) ? "followUp" as const : "steer" as const;
|
|
379
384
|
const pendingFollowUps = [...pending.values()].reduce((count, entries) => count + entries.filter((entry) => entry.deliveryStatus === "queued").length, 0);
|
|
380
385
|
if (delivery === "followUp" && queued.length + pendingFollowUps >= MAX_STEER_QUEUE_SIZE) {
|
|
381
386
|
acknowledge(request, "failed", `Follow-up queue is full (${MAX_STEER_QUEUE_SIZE} messages).`);
|
|
@@ -386,7 +391,7 @@ export function registerSteeringInbox(
|
|
|
386
391
|
entries.push({ request, deliveryStatus: delivery === "followUp" ? "queued" : "delivered" });
|
|
387
392
|
pending.set(formatted, entries);
|
|
388
393
|
try {
|
|
389
|
-
sendUserMessage(formatted,
|
|
394
|
+
sendUserMessage(formatted, autoCanUseIdle ? undefined : { deliverAs: delivery });
|
|
390
395
|
} catch (error) {
|
|
391
396
|
entries.pop();
|
|
392
397
|
if (entries.length === 0) pending.delete(formatted);
|
|
@@ -440,14 +445,71 @@ export function registerSteeringInbox(
|
|
|
440
445
|
flush();
|
|
441
446
|
return undefined;
|
|
442
447
|
};
|
|
448
|
+
const clearSettleFallback = (): void => {
|
|
449
|
+
if (!settleFallback) return;
|
|
450
|
+
clearTimeout(settleFallback);
|
|
451
|
+
settleFallback = undefined;
|
|
452
|
+
};
|
|
453
|
+
const markSettled = (): undefined => {
|
|
454
|
+
clearSettleFallback();
|
|
455
|
+
agentRunning = false;
|
|
456
|
+
inTurn = false;
|
|
457
|
+
awaitingSettlement = false;
|
|
458
|
+
return activate();
|
|
459
|
+
};
|
|
460
|
+
const armLegacySettleFallback = (): void => {
|
|
461
|
+
clearSettleFallback();
|
|
462
|
+
settleFallback = setTimeout(() => {
|
|
463
|
+
settleFallback = undefined;
|
|
464
|
+
if (disposed || !awaitingSettlement) return;
|
|
465
|
+
agentRunning = false;
|
|
466
|
+
inTurn = false;
|
|
467
|
+
awaitingSettlement = false;
|
|
468
|
+
activate();
|
|
469
|
+
}, legacySettleFallbackMs);
|
|
470
|
+
settleFallback.unref?.();
|
|
471
|
+
};
|
|
443
472
|
|
|
444
473
|
const onRuntimeEvent = pi.on as unknown as (event: string, handler: (event: unknown, ctx?: unknown) => unknown) => void;
|
|
445
474
|
// Register input before the watcher so an accepted extension input cannot race request dispatch.
|
|
446
475
|
onRuntimeEvent("input", onInput);
|
|
447
476
|
onRuntimeEvent("session_start", () => start());
|
|
448
|
-
onRuntimeEvent("agent_start", () => {
|
|
449
|
-
|
|
477
|
+
onRuntimeEvent("agent_start", () => {
|
|
478
|
+
clearSettleFallback();
|
|
479
|
+
agentRunning = true;
|
|
480
|
+
awaitingSettlement = false;
|
|
481
|
+
return activate();
|
|
482
|
+
});
|
|
483
|
+
onRuntimeEvent("agent_end", (event) => {
|
|
484
|
+
inTurn = false;
|
|
485
|
+
if ((event as { willRetry?: unknown } | undefined)?.willRetry === true) {
|
|
486
|
+
clearSettleFallback();
|
|
487
|
+
agentRunning = true;
|
|
488
|
+
awaitingSettlement = true;
|
|
489
|
+
return activate();
|
|
490
|
+
}
|
|
491
|
+
agentRunning = true;
|
|
492
|
+
awaitingSettlement = true;
|
|
493
|
+
armLegacySettleFallback();
|
|
494
|
+
return activate();
|
|
495
|
+
});
|
|
496
|
+
onRuntimeEvent("agent_settled", markSettled);
|
|
497
|
+
onRuntimeEvent("session_compact", () => {
|
|
498
|
+
const unresolved = [...pending.values()].flat();
|
|
499
|
+
pending.clear();
|
|
500
|
+
for (const entry of unresolved) {
|
|
501
|
+
try {
|
|
502
|
+
writeSteerRequestToDir(steerInbox, { ...entry.request, mode: "follow_up" });
|
|
503
|
+
} catch (error) {
|
|
504
|
+
acknowledge(entry.request, "failed", `Could not retry steering after compaction: ${error instanceof Error ? error.message : String(error)}`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return activate();
|
|
508
|
+
});
|
|
450
509
|
onRuntimeEvent("turn_start", () => {
|
|
510
|
+
clearSettleFallback();
|
|
511
|
+
agentRunning = true;
|
|
512
|
+
awaitingSettlement = false;
|
|
451
513
|
inTurn = true;
|
|
452
514
|
const next = queued.findIndex((entry) => entry.ready);
|
|
453
515
|
if (next >= 0) {
|
|
@@ -466,7 +528,11 @@ export function registerSteeringInbox(
|
|
|
466
528
|
}
|
|
467
529
|
onRuntimeEvent("session_shutdown", () => {
|
|
468
530
|
for (const entry of queued) acknowledge(entry.request, "failed", "Run ended before queued follow-up delivery.", "queued");
|
|
531
|
+
for (const entries of pending.values()) {
|
|
532
|
+
for (const entry of entries) acknowledge(entry.request, "failed", "Run ended before Pi confirmed steering input delivery.");
|
|
533
|
+
}
|
|
469
534
|
disposed = true;
|
|
535
|
+
clearSettleFallback();
|
|
470
536
|
try { watcher?.close(); } catch {}
|
|
471
537
|
if (interval) clearInterval(interval);
|
|
472
538
|
});
|
|
@@ -139,6 +139,25 @@ function assertJsonValue(value, path = "emit", seen = new Set()) {
|
|
|
139
139
|
seen.delete(value);
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
function isPlainWorkflowObject(value) {
|
|
143
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
144
|
+
const prototype = Object.getPrototypeOf(value);
|
|
145
|
+
return prototype === null || prototype === Object.prototype || prototype === contextObjectPrototype;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function omitUndefinedWorkflowValues(value, seen = new Set()) {
|
|
149
|
+
if (value === null || typeof value !== "object") return value;
|
|
150
|
+
if (seen.has(value)) return value;
|
|
151
|
+
seen.add(value);
|
|
152
|
+
const normalized = Array.isArray(value)
|
|
153
|
+
? value.map((entry) => entry === undefined ? null : omitUndefinedWorkflowValues(entry, seen))
|
|
154
|
+
: isPlainWorkflowObject(value) && Object.getOwnPropertySymbols(value).length === 0
|
|
155
|
+
? Object.fromEntries(Object.entries(value).flatMap(([key, entry]) => entry === undefined ? [] : [[key, omitUndefinedWorkflowValues(entry, seen)]]))
|
|
156
|
+
: value;
|
|
157
|
+
seen.delete(value);
|
|
158
|
+
return normalized;
|
|
159
|
+
}
|
|
160
|
+
|
|
142
161
|
parentPort.on("message", async (message) => {
|
|
143
162
|
if (message.type === "response") {
|
|
144
163
|
const entry = pending.get(message.callId);
|
|
@@ -163,7 +182,7 @@ parentPort.on("message", async (message) => {
|
|
|
163
182
|
return;
|
|
164
183
|
}
|
|
165
184
|
const value = await compiled.runInContext(context);
|
|
166
|
-
const persistedValue = value === undefined ? null : value;
|
|
185
|
+
const persistedValue = value === undefined ? null : omitUndefinedWorkflowValues(value);
|
|
167
186
|
assertJsonValue(persistedValue, "return");
|
|
168
187
|
parentPort.postMessage({ type: "complete", value: persistedValue });
|
|
169
188
|
} catch (error) {
|