Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions apps/hub/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,11 @@ import {
isWorkbenchHostDefinitionName,
listConnectedProviders,
listDefaultInferencePreferences,
recordSourcesDigest,
localPartOf,
parseParticipants,
postRoomMessage,
startWorkflowCommand,
settleConnectedService,
workbenchLaunchPersistExtra,
createCryptoProviderCache,
tagCredentialCipher,
verifyInternalRunTriggerToken,
Expand Down Expand Up @@ -2083,11 +2084,6 @@ export async function createHub(config: HubConfig) {
eventCollectors,
isRoutable: isSidecarRoutable,
cryptoProviderCache: cryptoProviders,
persistLaunch: async (input) => {
await workbenchLaunchPersistExtra(input)(db);
},
recordLaunchSources: ({ instanceId, sourcesDigest }) =>
recordSourcesDigest(db, instanceId, sourcesDigest),
},
trigger,
payload,
Expand Down
7 changes: 3 additions & 4 deletions packages/chat/src/agent-binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,10 +380,9 @@ export async function repointBinding(

/**
* Records the inference chain a deploy just pinned for the participant
* `stableId` names — a wake, or a standalone launch whose mapping row
* `workbenchLaunchPersistExtra` wrote before the deploy resolved — so
* the next send can tell whether the tenant's catalog (a rotated key, a
* moved endpoint) has moved on from it since.
* `stableId` names — a wake or a relaunch — so the next send can tell
* whether the tenant's catalog (a rotated key, a moved endpoint) has
* moved on from it since.
*/
export async function recordSourcesDigest(
db: DB["db"],
Expand Down
1 change: 0 additions & 1 deletion packages/chat/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ export {
createInMemoryTurnClaimStore,
} from "./turn-claims";
export type { TurnClaim, TurnClaimStore, TurnClaimToken } from "./turn-claims";
export { workbenchLaunchPersistExtra } from "./standalone-launch";
export {
AGENT_DM_DEFINITION_ID_KEY,
AGENT_DM_KIND,
Expand Down
45 changes: 0 additions & 45 deletions packages/chat/src/standalone-launch.test.ts

This file was deleted.

40 changes: 0 additions & 40 deletions packages/chat/src/standalone-launch.ts

This file was deleted.

34 changes: 0 additions & 34 deletions packages/webhook-triggers/src/launch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,37 +72,13 @@ export type LaunchWebhookTriggerDeps = {
* the composition root can keep passing the cipher it tagged at boot.
*/
credentialCipher: CredentialCipher;
/**
* Records the relaunch mapping after Interchange has prepared the
* run. Invoked with Interchange's returned `anchorRunId`, not a
* pre-minted id used as the run. No nested transaction: the host
* writes after prepare has already committed the run rows.
*/
persistLaunch: (input: {
readonly tenantId: string;
readonly instanceId: string;
readonly foldedBody: ReturnType<typeof readFoldedBody>;
}) => void | Promise<void>;
/**
* Records the inference chain the launch just deployed with.
* Digest is the joined catalog offering ids, same as chat's
* `offeringDigest`.
*/
recordLaunchSources: (input: {
readonly instanceId: string;
readonly sourcesDigest: string;
}) => Promise<void>;
};

export type LaunchedWebhookTrigger = {
readonly instanceId: string;
readonly triggerAddress: string;
};

function offeringDigest(sourceOfferingIds: readonly string[]): string {
return sourceOfferingIds.join("\0");
}

/**
* Resolves the trigger's referenced workflow definition (must be
* deployed and materialized), provisions its existing asset HEAD
Expand Down Expand Up @@ -213,16 +189,6 @@ export async function launchWebhookTrigger(
sourceAuthorityPrincipalId: trigger.createdBy,
});

await deps.persistLaunch({
tenantId: trigger.tenantId,
instanceId: prepared.anchorRunId,
foldedBody,
});
await deps.recordLaunchSources({
instanceId: prepared.anchorRunId,
sourcesDigest: offeringDigest(sourceOfferingIds),
});

const content = renderInputTemplate(trigger.inputTemplate, payload);
const cryptoProvider = await deps.cryptoProviderCache.get(
prepared.anchorRunId,
Expand Down
37 changes: 8 additions & 29 deletions packages/webhook-triggers/test/launch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,6 @@ const INERT_PROJECTION = {
credentialBindings: [],
};

const EXPECTED_FOLDED_BODY = {
systemPrompt: "you are a webhook-triggered agent",
toolPackagePins: [],
grantRequirements: [],
credentialBindings: [],
model: "claude-sonnet-5",
};

const DEFAULT_VISIBLE_OFFERINGS = [
{
offering: { id: "off_1", priority: 0 },
Expand Down Expand Up @@ -154,8 +146,6 @@ type PrepareArgs = {
readonly toolPackagePins?: readonly unknown[];
};

let persistLaunchCalls: unknown[] = [];
let recordLaunchSourcesCalls: unknown[] = [];
let resolveRefCalls: unknown[] = [];
let prepareCalls: PrepareArgs[] = [];
let sendUserMessageCalls: unknown[] = [];
Expand All @@ -174,12 +164,6 @@ function baseDeps() {
return {} as never;
},
},
persistLaunch: async (input: unknown) => {
persistLaunchCalls.push(input);
},
recordLaunchSources: async (input: unknown) => {
recordLaunchSourcesCalls.push(input);
},
repoStore: {
resolveRef: async (...args: unknown[]) => {
resolveRefCalls.push(args);
Expand Down Expand Up @@ -209,8 +193,6 @@ function baseDeps() {
}

function resetLaunchSpies() {
persistLaunchCalls = [];
recordLaunchSourcesCalls = [];
resolveRefCalls = [];
prepareCalls = [];
sendUserMessageCalls = [];
Expand Down Expand Up @@ -376,24 +358,21 @@ describe("launchWebhookTrigger", () => {
expect(prepareCalls[0]?.toolPackagePins).toBeUndefined();
});

test("persists the relaunch mapping against Interchange's returned run id", async () => {
// CL-6534 part 1: a webhook delivery is a one-off native run — the
// native `workflow_run` row Interchange commits answers identity, so
// launch provisions it and delivers the opening mail with no
// `workbench_launch` mirror write (the deps surface no longer carries
// `persistLaunch`/`recordLaunchSources` at all).
test("provisions the native run with no workbench_launch mirror write", async () => {
resetLaunchSpies();

const result = await launchWebhookTrigger(baseDeps(), TRIGGER, {
status: "ok",
});

expect(persistLaunchCalls).toEqual([
{
tenantId: "ten_1",
instanceId: INTERCHANGE_RUN_ID,
foldedBody: EXPECTED_FOLDED_BODY,
},
]);
expect(recordLaunchSourcesCalls).toEqual([
{ instanceId: INTERCHANGE_RUN_ID, sourcesDigest: "off_1\0off_2" },
]);
expect(result.instanceId).toBe(INTERCHANGE_RUN_ID);
expect(prepareCalls).toHaveLength(1);
expect(sendUserMessageCalls).toHaveLength(1);
expect(cryptoGetKeys).toEqual([INTERCHANGE_RUN_ID]);
});
});
28 changes: 1 addition & 27 deletions packages/workflows/src/definition-projection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
// persisted (`agent.modelSources`, no `grantRequirements` — the
// projector drops it, so the definition row supplies it), and fails
// loud on a malformed projection, a multi-step one, or a step that is
// not a step primitive. `readLiveFoldedBody` reads the pre-projection
// live shape the in-process workbench-host launch still carries.
// not a step primitive.
//
// `resolveNewestProjectedDefinition` is the DB-side successor to
// CL-6357's asset-drift walk: a pre-cutover sibling carrying no stored
Expand Down Expand Up @@ -39,7 +38,6 @@ afterEach(() => mock.restore());
const {
authoredDefinitionCandidates,
readFoldedBody,
readLiveFoldedBody,
readDefinitionProjection,
resolveNewestProjectedDefinition,
DefinitionProjectionMissingError,
Expand Down Expand Up @@ -241,30 +239,6 @@ describe("readFoldedBody", () => {
});
});

describe("readLiveFoldedBody", () => {
test("extracts the launch body from the in-process live definition shape", () => {
expect(readLiveFoldedBody(liveDefinition())).toEqual({
systemPrompt: "you are a workbench host",
toolPackagePins: [],
grantRequirements: [],
credentialBindings: [],
model: "qwen3:8b",
});
});

test("fails loud on a malformed definition", () => {
expect(() => readLiveFoldedBody({ not: "a definition" })).toThrow(
/live definition is malformed/,
);
});

test("raises the named MultiStepFoldUnsupportedError on a multi-step live definition", () => {
expect(() =>
readLiveFoldedBody(liveDefinition({ stepOrder: ["host", "second"] })),
).toThrow(MultiStepFoldUnsupportedError);
});
});

// CL-6452: every run deploy mints a same-named, same-asset sibling
// definition row (its per-run rendered bytes carry a per-run wire hash),
// frozen with the projection current AT THAT DEPLOY. Those run clones
Expand Down
67 changes: 0 additions & 67 deletions packages/workflows/src/definition-projection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,34 +223,6 @@ const InertWorkflowDefinitionSchema = type({
"credentialBindings?": CredentialBinding.array(),
});

/**
* The launch-relevant subset of a LIVE serialized `WorkflowDefinition`'s
* step — the pre-projection shape, where the inference chain is still
* nested at `agent.inference.sources`. This is not an alternative source
* for a deployed definition's body: it serves the one caller that builds
* its definition in process and launches it in the same breath (the
* workbench host, `buildWorkbenchHostWorkflow`), which has the live
* object in hand and never round-trips through a deploy freeze.
*/
const LiveWorkflowStepSchema = type({
kind: "'step'",
agent: {
systemPrompt: "string",
"toolPackagePins?": ToolPackagePin.array(),
inference: {
sources: type({ "model?": "string | null" }).array(),
},
},
});

const LiveWorkflowDefinitionSchema = type({
id: "string",
stepOrder: "string[]",
steps: "Record<string, unknown>",
"grantRequirements?": GrantRequirement.array(),
"credentialBindings?": CredentialBinding.array(),
});

export const FoldedBodySchema = type({
systemPrompt: "string",
toolPackagePins: ToolPackagePin.array(),
Expand Down Expand Up @@ -301,42 +273,3 @@ export function readFoldedBody(
}
return foldedBody;
}

/**
* Reads the launch body out of a live serialized `WorkflowDefinition` —
* the in-process launch path described on `LiveWorkflowStepSchema`.
* Unlike the projection, a live definition still carries its own
* `grantRequirements`, so nothing is passed in beside it.
*/
export function readLiveFoldedBody(raw: unknown): FoldedBody {
const definition = LiveWorkflowDefinitionSchema(raw);
if (definition instanceof type.errors) {
throw new Error(`live definition is malformed: ${definition.summary}`);
}
const [stepId, ...rest] = definition.stepOrder;
if (stepId === undefined || rest.length > 0) {
throw new MultiStepFoldUnsupportedError(
definition.id,
definition.stepOrder.length,
);
}
const step = LiveWorkflowStepSchema(definition.steps[stepId]);
if (step instanceof type.errors) {
throw new Error(
`live definition ${definition.id} step ${stepId} is not a step primitive: ${step.summary}`,
);
}
const foldedBody = FoldedBodySchema({
systemPrompt: step.agent.systemPrompt,
toolPackagePins: step.agent.toolPackagePins ?? [],
grantRequirements: definition.grantRequirements ?? [],
credentialBindings: definition.credentialBindings ?? [],
model: step.agent.inference.sources[0]?.model ?? null,
});
if (foldedBody instanceof type.errors) {
throw new Error(
`live definition ${definition.id} produced an invalid folded body: ${foldedBody.summary}`,
);
}
return foldedBody;
}
Loading
Loading