Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
b1e6caa
Add test for workflowDeployBody's catalog-offering shape
TheGreatAxios Sep 6, 2026
fae77c0
e2e: deploy workflows by catalog offering ids
TheGreatAxios Sep 6, 2026
b03ce16
Fix formatting
TheGreatAxios Sep 6, 2026
f981798
Add tests for deploying default workflows by catalog offering ids
TheGreatAxios Sep 6, 2026
1ce35c4
Seeding: deploy default workflows by catalog offering ids
TheGreatAxios Sep 6, 2026
2587cd2
longevity-sim: deploy workflows by catalog offering ids
TheGreatAxios Sep 6, 2026
2430449
Fix formatting: prettier
TheGreatAxios Sep 6, 2026
b65781e
Merge remote-tracking branch 'origin/cl-7461-seed-deploy-offering-ids…
TheGreatAxios Sep 6, 2026
602ad68
sandbox-sidecar: add tests for generation-0 ensure/destroy requests
TheGreatAxios Sep 6, 2026
e01feda
sandbox-sidecar: accept the allocation service's initial generation
TheGreatAxios Sep 6, 2026
c6da1fa
sandbox-sidecar: fix formatting in provisioner test
TheGreatAxios Sep 6, 2026
72d5be0
Merge remote-tracking branch 'origin/cl-7107-repin-and-re-vendor-audi…
TheGreatAxios Sep 6, 2026
824c2b6
Add tests for resolved catalog offerings and modelSourceFor's trimmed…
TheGreatAxios Sep 6, 2026
ec87551
Seed deploys against a tenant's resolved catalog offerings, not just …
TheGreatAxios Sep 6, 2026
dd1fe50
Merge remote-tracking branch 'origin/cl-7461-seed-deploy-offering-ids…
TheGreatAxios Sep 6, 2026
bcb8703
Add a regression test pinning the shared-sidecar auth gap (CL-7472)
TheGreatAxios Sep 6, 2026
ccbe497
e2e: stop pre-provisioning a shared sidecar identity (CL-7472)
TheGreatAxios Sep 6, 2026
308ab25
ci: upload hub/sidecar e2e logs as a build artifact (CL-7472)
TheGreatAxios Sep 6, 2026
27b8f84
Merge remote-tracking branch 'origin/cl-7107-repin-and-re-vendor-audi…
TheGreatAxios Sep 6, 2026
b72964e
Add tests for tenant-scoped workbench_launch address resolution (CL-7…
TheGreatAxios Sep 6, 2026
262a381
Chat: workbench_launch address/stableId lookups are tenant-scoped
TheGreatAxios Sep 6, 2026
b75eebc
Chat: run prettier on the tenant-scoping change
TheGreatAxios Sep 6, 2026
040f085
Merge remote-tracking branch 'origin/cl-7107-repin-and-re-vendor-audi…
TheGreatAxios Sep 6, 2026
75300cb
Merge the re-pin branch into cl-7474-cross-tenant-launch
TheGreatAxios Sep 6, 2026
0eef0e0
Chat: tenant-scoped binding reads require the tenant; the one unscope…
TheGreatAxios Sep 6, 2026
b677451
Add tests for the shared retry-until-routable delivery helper
TheGreatAxios Sep 6, 2026
5cb3164
Deliver a webhook trigger's opening mail once its sidecar becomes rou…
TheGreatAxios Sep 6, 2026
a0c8a27
Log a skip, not an error, when root tenant seed has no operator key c…
TheGreatAxios Sep 6, 2026
ff22eef
Merge remote-tracking branch 'origin/cl-7474-cross-tenant-launch' int…
TheGreatAxios Sep 6, 2026
1fd89dd
Run prettier
TheGreatAxios Sep 6, 2026
3f8fdc9
Chat: run prettier on the tenant-scoping test file
TheGreatAxios Sep 6, 2026
3cf5507
Merge remote-tracking branches 'origin/cl-7474-cross-tenant-launch' a…
TheGreatAxios Sep 6, 2026
46a4a93
Add a DB-gated test for recording a run's agent_session (CL-7477)
TheGreatAxios Sep 6, 2026
39ea012
Native launch records the run's agent_session so mail and spans persi…
TheGreatAxios Sep 6, 2026
5f92950
Merge remote-tracking branch 'origin/cl-7107-repin-and-re-vendor-audi…
TheGreatAxios Sep 6, 2026
f31e0e1
Add tests for the run's event collector create/abandon wiring
TheGreatAxios Sep 6, 2026
9260c3a
Native launch creates the run's event collector so turns are recorded…
TheGreatAxios Sep 6, 2026
3706a53
Merge remote-tracking branch 'origin/cl-7107-repin-and-re-vendor-audi…
TheGreatAxios Sep 6, 2026
f6369d9
Add tests for lazy, idempotent run-session ensure (CL-7480)
TheGreatAxios Sep 6, 2026
d6eda39
Hub ensures a run's session and collector on first use (CL-7480)
TheGreatAxios Sep 6, 2026
63b2d2e
Fix prettier formatting in agent-session.ts (CL-7480)
TheGreatAxios Sep 6, 2026
af790b2
Add tests for eager session recording and launch-spec session lookup
TheGreatAxios Sep 6, 2026
ae8d33b
Record a run's session at provision; re-key it when Interchange ancho…
TheGreatAxios Sep 6, 2026
2b5dd7a
Fix CI: prettier formatting, principal enum value, and workflowRunLau…
TheGreatAxios Sep 6, 2026
258cfe4
Merge remote-tracking branch 'origin/cl-7107-repin-and-re-vendor-audi…
TheGreatAxios Sep 6, 2026
8b469ac
Merge the re-pin branch into cl-7481-eager-session
TheGreatAxios Sep 6, 2026
c57dd9d
Fix merge fallout: restore CL-7481 agent-session exports and drop a d…
TheGreatAxios Sep 6, 2026
42f0511
Add tests for wake retry re-resolving the live address (CL-7486)
TheGreatAxios Sep 6, 2026
c5cbceb
Chat: wake retry chases the run a relaunch replaced it with (CL-7486)
TheGreatAxios Sep 6, 2026
485dc99
Merge remote-tracking branch 'origin/cl-7481-eager-session' into cl-7…
TheGreatAxios Sep 6, 2026
ebc173b
Fix webhook-triggers launch test fixture: eventCollectors needs has()
TheGreatAxios Sep 6, 2026
097b172
Add test coverage for routable-wait relaunch chasing in sendRunMailWi…
TheGreatAxios Sep 6, 2026
153b818
Chat: wait for the routing table instead of a fixed backoff before gi…
TheGreatAxios Sep 6, 2026
320deb2
Fix formatting
TheGreatAxios Sep 6, 2026
632b439
Merge the re-pin branch into cl-7488-chat-routable
TheGreatAxios Sep 6, 2026
3cfb6de
Add test coverage for ensureRunSession creating a session for an API-…
TheGreatAxios Sep 6, 2026
6547f36
ensureRunSession: create the session row instead of throwing when it'…
TheGreatAxios Sep 6, 2026
b9785a9
Add tests for waiting on a booting run instead of relaunching it
TheGreatAxios Sep 6, 2026
a1370dc
Chat: a booting run is waited on, not relaunched
TheGreatAxios Sep 6, 2026
e3433ca
Merge remote-tracking branch 'origin/cl-7107-repin-and-re-vendor-audi…
TheGreatAxios Sep 6, 2026
b913397
Add tests for chat's run-trigger delivery (CL-7490)
TheGreatAxios Sep 6, 2026
651ea2e
Chat delivers room mail through Interchange's run-trigger route (CL-7…
TheGreatAxios Sep 6, 2026
6122e09
Fix lint formatting and report-error finding in run-trigger-client
TheGreatAxios Sep 6, 2026
592e741
Merge remote-tracking branch 'origin/cl-7107-repin-and-re-vendor-audi…
TheGreatAxios Sep 6, 2026
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
53 changes: 52 additions & 1 deletion apps/hub/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
model,
modelPricing,
tenant as tenantTable,
user as userTable,
workflowDefinition,
} from "@intx/db/schema";
import { and, eq, inArray } from "drizzle-orm";
Expand Down Expand Up @@ -92,6 +93,7 @@ import {
createHubChatPlatform,
createNoopInferenceRoutes,
createRelaunchNoticePoster,
createRunTriggerClient,
createWorkflowParticipantRoutes,
isWorkbenchHostDefinitionName,
listConnectedProviders,
Expand All @@ -105,6 +107,7 @@ import {
workbenchLaunchPersistExtra,
createCryptoProviderCache,
tagCredentialCipher,
verifyInternalRunTriggerToken,
} from "@corbits/chat";
import {
createDrizzleMailboxWriter,
Expand Down Expand Up @@ -1129,7 +1132,47 @@ export async function createHub(config: HubConfig) {
workflowAllocationService,
workflowDispatchService,
credentialCipher,
// `@intx/hub-api`'s `GetSession` is a deliberately pluggable seam
// (its own doc comment: "so a third-party identity provider can be
// plugged in"), and this composition root owns it. `@corbits/chat`'s
// run-trigger client (CL-7490) has no browser cookie to present for
// most of its calls — mention fan-out, a relaunch resend, the
// mailbox-fanout persist seam all run with no inbound HTTP request
// in scope — so it signs a short-lived internal token instead (see
// `@corbits/chat`'s `run-trigger-internal-auth.ts`) naming the
// better-auth user it authenticates as. This checks that token
// FIRST and only falls through to the real cookie-session path when
// the header is absent; a PRESENT-but-invalid token fails closed
// (401 via a null session) rather than silently retrying as a
// session, the same posture `workflow-run-deploy-auth` documents for
// its own bearer mirror in `vendor/intx/hub-api`.
getSession: async (headers) => {
const internalRunTriggerToken = headers.get(
"x-corbits-internal-run-trigger",
);
if (internalRunTriggerToken !== null) {
const userId = verifyInternalRunTriggerToken(
config.sessionSecret,
internalRunTriggerToken,
);
if (userId === null) return null;
const userRow = await db.query.user.findFirst({
where: eq(userTable.id, userId),
});
if (userRow === undefined) return null;
const now = new Date();
return {
user: userRow,
session: {
id: `internal_run_trigger_${userRow.id}`,
createdAt: now,
updatedAt: now,
userId: userRow.id,
expiresAt: new Date(now.getTime() + 30_000),
token: internalRunTriggerToken,
},
};
}
const result = await auth.api.getSession({ headers });
return result ? { user: result.user, session: result.session } : null;
},
Expand Down Expand Up @@ -1430,9 +1473,17 @@ export async function createHub(config: HubConfig) {
// across consumers. TTL-bounded by `createCryptoProviderCache` itself
// (CL-7223).
const cryptoProviders = createCryptoProviderCache();
// Delivers chat's outbound run mail through Interchange's own
// workflow-run mail-trigger route (CL-7490), in-process against this
// same `app` — see `@corbits/chat`'s `run-trigger-client.ts` and the
// `getSession` internal-token check above for how it authenticates.
const runTrigger = createRunTriggerClient({
app,
internalAuthSecret: config.sessionSecret,
});
const chatPlatform = createHubChatPlatform({
db,
sessionService,
runTrigger,
repoStore: agentRepoStore.repoStore,
sidecarRouter,
eventCollectors,
Expand Down
11 changes: 11 additions & 0 deletions packages/chat/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,17 @@ export type {
CreateHubChatPlatformDeps,
HubChatPlatform,
} from "./platform-adapter";
export { createRunTriggerClient, RunTriggerError } from "./run-trigger-client";
export type {
RunTriggerClient,
RunTriggerClientDeps,
TriggerWorkflowRunMailInput,
TriggeredWorkflowRunMail,
} from "./run-trigger-client";
export {
signInternalRunTriggerToken,
verifyInternalRunTriggerToken,
} from "./run-trigger-internal-auth";
export { createCryptoProviderCache } from "./crypto-cache";
export type { CryptoProviderCache } from "./crypto-cache";
export { tagCredentialCipher } from "./credential-cipher-tag";
Expand Down
88 changes: 84 additions & 4 deletions packages/chat/src/platform-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import type { CryptoProviderCache } from "./crypto-cache";
import { InferenceResolutionError } from "./model-unavailable";
import type { ToolGrantsForPins } from "./pin-ports";
import { resolveRunSessionIdOrThrow } from "./run-session";
import type { RunTriggerClient } from "./run-trigger-client";
import { sendRunMail, type SendRunMailParams } from "./send-run-mail";
import {
findStandingLaunchByDefinition,
Expand All @@ -53,6 +54,7 @@ import type { DB } from "@intx/db";
import { listVisibleOfferings } from "@intx/db";
import {
asset as assetTable,
principal as principalTable,
sessionMail,
tenant as tenantTable,
workflowDefinition,
Expand All @@ -68,7 +70,6 @@ import {
DEFAULT_ASSET_REF,
type EventCollectorRegistry,
type RepoStore,
type SessionService,
type SidecarRouter,
type WorkflowAllocationService,
} from "@intx/hub-sessions";
Expand All @@ -94,7 +95,15 @@ function domainOf(address: string): string {

export type CreateHubChatPlatformDeps = {
db: DB["db"];
sessionService: Pick<SessionService, "sendUserMessage">;
/**
* Delivers outbound run mail through Interchange's own workflow-run
* mail-trigger route (CL-7490) — see `./run-trigger-client.ts` for why
* a raw `sessionService.sendUserMessage` can no longer stand in for
* this: it never reaches the trigger route's durable-dispatch branch,
* so a provisioned run invited but never yet triggered natively never
* gets its first `agent.deploy`.
*/
runTrigger: RunTriggerClient;
repoStore: Pick<RepoStore, "resolveRef">;
/**
* Interchange's provisioned-deployment front. Invite, wake, and
Expand Down Expand Up @@ -263,10 +272,68 @@ export function createHubChatPlatform(
void tagCredentialCipher(deps.credentialCipher);
const runMailDeps = {
db: deps.db,
sessionService: deps.sessionService,
runTrigger: deps.runTrigger,
sidecarRouter: deps.sidecarRouter,
};

/**
* Resolves the principal a run-trigger's DB row names, then the
* better-auth `user.id` behind it — the identity
* `./run-trigger-client.ts` authenticates the trigger call as. Only a
* `kind: "user"` principal has a real login identity to authenticate
* as; anything else is a wiring bug this throws loud on rather than
* silently authenticating as nobody.
*/
async function refIdForUserPrincipal(principalId: string): Promise<string> {
const rows = await deps.db
.select({ refId: principalTable.refId, kind: principalTable.kind })
.from(principalTable)
.where(eq(principalTable.id, principalId))
.limit(1);
const row = rows[0];
if (row === undefined || row.kind !== "user") {
throw new Error(
`principal "${principalId}" is not a user principal chat can authenticate the run trigger as`,
);
}
return row.refId;
}

/**
* The identity a `sendMail` call authenticates its run-trigger
* delivery as (see `./run-trigger-client.ts`'s `authAsUserId`):
* the sending human when `sendMail` named one directly, and
* otherwise the run's own workflow definition asset's
* `creatorPrincipalId` — the same authority
* `sourceAuthorityForAsset` already resolves for provisioning.
*/
async function resolveTriggerAuthUserId(input: {
readonly principalId: string | undefined;
readonly definitionId: string | null;
}): Promise<string> {
if (input.principalId !== undefined) {
return refIdForUserPrincipal(input.principalId);
}
if (input.definitionId === null) {
throw new Error(
"run has no definitionId to resolve a run-trigger authority from",
);
}
const rows = await deps.db
.select({ assetId: workflowDefinition.assetId })
.from(workflowDefinition)
.where(eq(workflowDefinition.id, input.definitionId))
.limit(1);
const assetId = rows[0]?.assetId;
if (assetId === null || assetId === undefined) {
throw new Error(
`workflow definition "${input.definitionId}" has no asset`,
);
}
const creatorPrincipalId = await sourceAuthorityForAsset(assetId);
return refIdForUserPrincipal(creatorPrincipalId);
}

function offeringDigest(sourceOfferingIds: readonly string[]): string {
// `sources_digest` is a `text` column and this digest rides straight
// into it (`platform-adapter.ts`'s `insert(workbenchLaunch)`). A
Expand Down Expand Up @@ -974,7 +1041,11 @@ export function createHubChatPlatform(
*/
async function sendRunMailWithReclaimRetry(
params: SendRunMailParams,
reresolve: () => Promise<{ agentAddress: string; sessionId: string }>,
reresolve: () => Promise<{
agentAddress: string;
anchorRunId: string;
sessionId: string;
}>,
): Promise<Awaited<ReturnType<typeof sendRunMail>>> {
let current = params;
let wakedOnFailure = false;
Expand Down Expand Up @@ -1012,6 +1083,7 @@ export function createHubChatPlatform(
current = {
...current,
agentAddress: fresh.agentAddress,
anchorRunId: fresh.anchorRunId,
sessionId: fresh.sessionId,
};
throw err;
Expand Down Expand Up @@ -1370,14 +1442,21 @@ export function createHubChatPlatform(
}),
);

const authAsUserId = await resolveTriggerAuthUserId({
principalId: input.principalId,
definitionId: delivery.run.definitionId,
});

const sendMailBase = {
tenantId: input.tenantId,
sessionId,
agentAddress: deliveryAddress,
anchorRunId: delivery.run.id,
from,
domain,
content: input.content.content,
cryptoProvider,
authAsUserId,
};
const withAttachments =
attachments !== undefined
Expand Down Expand Up @@ -1424,6 +1503,7 @@ export function createHubChatPlatform(
}
return {
agentAddress: relive.binding.liveAddress,
anchorRunId: relive.run.id,
sessionId: await resolveRunSessionIdOrThrow(deps.db, relive.run),
};
},
Expand Down
Loading
Loading