diff --git a/VENDORED.md b/VENDORED.md index dced992a5..e17a354fb 100644 --- a/VENDORED.md +++ b/VENDORED.md @@ -42,7 +42,7 @@ Pinned to [faremeter/interchange](https://github.com/faremeter/interchange) | `vendor/intx/hub-common` | `@intx/hub-common` at a newer commit than npm has published | maintainers | 2026-11-03 | | `vendor/intx/workflow` | `@intx/workflow`, with one local step-timeout-budget delta | maintainers | 2026-11-03 | | `vendor/intx/workflow-deploy` | `@intx/workflow-deploy` at a newer commit than npm has published | maintainers | 2026-11-03 | -| `vendor/intx/workflow-host` | `@intx/workflow-host`, with one local step-grants-collapse delta | maintainers | 2026-11-03 | +| `vendor/intx/workflow-host` | `@intx/workflow-host`, with a local step-grants-collapse delta and a temporary delta omitting an empty inbound `Subject` header instead of passing it through empty (kill condition: upstream tolerates an empty Subject, INTR-577) | maintainers | 2026-11-03 | npm's published `0.3.0` predates this pin for every row above, so each is re-vendored at the same commit rather than mixed pins; the root diff --git a/vendor/intx/workflow-host/VENDORED-FROM b/vendor/intx/workflow-host/VENDORED-FROM index 0831830e4..0b3edb299 100644 --- a/vendor/intx/workflow-host/VENDORED-FROM +++ b/vendor/intx/workflow-host/VENDORED-FROM @@ -1,4 +1,4 @@ Source: https://github.com/faremeter/interchange (packages/workflow-host) Commit: 692c31068cbb636ebb19c6d179ec6fe126a2d4e6 (origin/main, 2026-09-03) License: LGPL-2.1-only (see vendor/intx/LICENSE) -Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. Upstream test files are not vendored (they import the unvendored @intx/inference-testing and @intx/test-harness test packages); only the tests covering a workbench delta are kept. CL-6448: the suspendable-child (onTrigger body) spawn seam threads the parent child's credentials-backed authorize and MailPartReader: RunSuspendableChild's input and createInMemorySpawnSuspendableChild's opts gain the two optional fields (adapters/spawn-child.ts), and child/run-child.ts hoists `authorize` above the body resolver and passes both when building it, so a tool-bearing body agent gates its tool calls through the same per-step grant snapshot a top-level step does and reads an attachments-only inbound mail's parts instead of throwing. Upstream runs bodies toolless and has no analog. child/run-child.ts also resolves a step's grants entry through findStepGrantsEntry (head collapse: a body step whose own id is absent from a single-step deployment's snapshot resolves to the sole entry). CL-6396: the event-channel envelope, sender, and receiver carry an optional childRunId; run-child stamps spawnInput.childRunId onto body-child events so overlapping turns HMAC-bind the occurrence they belong to. Retired at this pin: the CL-6448 credentialWiring thread (upstream now passes the run's live credential-material cell into every body spawn natively as `credentialMaterial`). Retired at earlier pins: the empty-mail drop (CL-6164) and the action/loop runtime bind (CL-6325). +Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. Upstream test files are not vendored (they import the unvendored @intx/inference-testing and @intx/test-harness test packages); only the tests covering a workbench delta are kept. CL-6448: the suspendable-child (onTrigger body) spawn seam threads the parent child's credentials-backed authorize and MailPartReader: RunSuspendableChild's input and createInMemorySpawnSuspendableChild's opts gain the two optional fields (adapters/spawn-child.ts), and child/run-child.ts hoists `authorize` above the body resolver and passes both when building it, so a tool-bearing body agent gates its tool calls through the same per-step grant snapshot a top-level step does and reads an attachments-only inbound mail's parts instead of throwing. Upstream runs bodies toolless and has no analog. child/run-child.ts also resolves a step's grants entry through findStepGrantsEntry (head collapse: a body step whose own id is absent from a single-step deployment's snapshot resolves to the sole entry). CL-6396: the event-channel envelope, sender, and receiver carry an optional childRunId; run-child stamps spawnInput.childRunId onto body-child events so overlapping turns HMAC-bind the occurrence they belong to. Retired at this pin: the CL-6448 credentialWiring thread (upstream now passes the run's live credential-material cell into every body spawn natively as `credentialMaterial`). Retired at earlier pins: the empty-mail drop (CL-6164) and the action/loop runtime bind (CL-6325). Temporary delta: buildInboundMessageFromMail omits `subject` when the decoded header is empty (a present-but-textless Subject header), since createInboundMessage rejects an empty subject and fails the step; see VENDORED.md's kill condition. diff --git a/vendor/intx/workflow-host/src/adapters/step-invoker.test.ts b/vendor/intx/workflow-host/src/adapters/step-invoker.test.ts new file mode 100644 index 000000000..b9efd2306 --- /dev/null +++ b/vendor/intx/workflow-host/src/adapters/step-invoker.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, test } from "bun:test"; + +import type { Mail } from "@intx/types/runtime"; + +import { buildInboundMessageFromMail } from "./step-invoker"; + +function mailWithSubject(subject: string | undefined): Mail { + return { + headers: { + from: "sender@example.com", + to: ["agent@local"], + ...(subject !== undefined ? { subject } : {}), + }, + rawHeaders: {}, + parts: [{ contentType: "text/plain", ref: "part_1", text: "hi" }], + } as Mail; +} + +describe("buildInboundMessageFromMail", () => { + test("an empty Subject header is treated as absent", async () => { + const message = await buildInboundMessageFromMail( + mailWithSubject(""), + undefined, + ); + + expect(message.headers.subject).toBeUndefined(); + }); +}); diff --git a/vendor/intx/workflow-host/src/adapters/step-invoker.ts b/vendor/intx/workflow-host/src/adapters/step-invoker.ts index 986d241b5..59d2a66d4 100644 --- a/vendor/intx/workflow-host/src/adapters/step-invoker.ts +++ b/vendor/intx/workflow-host/src/adapters/step-invoker.ts @@ -770,7 +770,7 @@ function safeAddr(raw: string | undefined, fallback: string): string { * which is not wired with a reader -- still delivers; a part that needs bytes * with no reader is refused loudly rather than silently dropped. */ -async function buildInboundMessageFromMail( +export async function buildInboundMessageFromMail( mail: Mail, mailPartReader: MailPartReader | undefined, ): Promise { @@ -819,7 +819,11 @@ async function buildInboundMessageFromMail( return createInboundMessage({ from: safeAddr(mail.headers.from, "trigger@local"), to: safeAddr(mail.headers.to[0], "agent@local"), - ...(mail.headers.subject !== undefined + // A present-but-empty Subject header (e.g. "Subject:" with no text) + // decodes to "" and createInboundMessage rejects an empty subject, + // failing the step -- so treat it like an absent header. + ...(mail.headers.subject !== undefined && + mail.headers.subject.trim().length > 0 ? { subject: mail.headers.subject } : {}), ...(isMessageId(mail.headers.messageId)