feat(agent): agent.spawn{await:true} suspends too; children wake the parent via fact

agent.await and agent.spawn{await:true} now return immediately when
children are still running: the session closes, runAgent registers a
wake signal in agent_run.await_wake and waits with waitForFact without
holding an io/agent worker slot. A child that settles (terminal or
waiting) signals the parent's registered fact, so the parent resumes at
once instead of polling; the suspended call replays and the child
report becomes the tool result. agent.spawn is registered replay-safe;
a suspended spawn is recorded as failed in the ledger so the replay
re-runs it against the same child.

Migration 0037 also adds the admin_recovery_code, admin_passkey and
webauthn_challenge tables used by the account work.

E11 asserts the parent is suspended with no run held by any worker
while it waits, and covers spawn{await:true} end to end.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-28 20:21:52 +08:00
1 parent f9b30b7e72
commit 5c6f483420
8 files changed
+382 -28

No files matched your search

+47
View File
@@ -246,6 +246,7 @@ export type TaskStatus = "pending" | "running" | "waiting" | "succeeded" | "fail
export type TasksOpLedgerStatus = "started" | "succeeded";
export type TitleAliasKind = "original" | "translated" | "short" | "site_title" | "romanized" | "other";
export type TitleAliasSource = "tmdb" | "user" | "site" | "agent" | "nfo" | "import";
export type WebauthnChallengePurpose = "register" | "login";
export interface AcquisitionPlanTable {
id: Generated<string>;
@@ -296,6 +297,29 @@ export interface AcquisitionSlotTable {
position: Generated<number>;
}
export interface AdminPasskeyTable {
id: Generated<string>;
admin_user_id: string;
credential_id: string;
public_key: Buffer;
counter: Generated<number>;
transports: Generated<Json>;
device_type: string | null;
backed_up: Generated<boolean>;
name: string;
last_used_at: Timestamptz | null;
created_at: GeneratedTimestamptz;
updated_at: GeneratedTimestamptz;
}
export interface AdminRecoveryCodeTable {
id: Generated<string>;
admin_user_id: string;
code_hash: string;
used_at: Timestamptz | null;
created_at: GeneratedTimestamptz;
}
export interface AdminSessionTable {
id: Generated<string>;
admin_user_id: string;
@@ -355,6 +379,7 @@ export interface AgentRunTable {
upstream_id: string | null;
depth: Generated<number>;
spawn_reason: string | null;
await_wake: Json | null;
}
export interface AgentRunEventTable {
@@ -1552,9 +1577,21 @@ export interface TrashItemTable {
updated_at: GeneratedTimestamptz;
}
export interface WebauthnChallengeTable {
id: Generated<string>;
purpose: WebauthnChallengePurpose;
challenge: string;
admin_user_id: string | null;
expires_at: Timestamptz;
used_at: Timestamptz | null;
created_at: GeneratedTimestamptz;
}
export interface DB {
acquisition_plan: AcquisitionPlanTable;
acquisition_slot: AcquisitionSlotTable;
admin_passkey: AdminPasskeyTable;
admin_recovery_code: AdminRecoveryCodeTable;
admin_session: AdminSessionTable;
admin_user: AdminUserTable;
agent_run: AgentRunTable;
@@ -1627,6 +1664,7 @@ export interface DB {
tasks_run_meta: TasksRunMetaTable;
title_alias: TitleAliasTable;
trash_item: TrashItemTable;
webauthn_challenge: WebauthnChallengeTable;
}
export type AcquisitionPlanRow = Selectable<AcquisitionPlanTable>;
@@ -1635,6 +1673,12 @@ export type AcquisitionPlanUpdate = Updateable<AcquisitionPlanTable>;
export type AcquisitionSlotRow = Selectable<AcquisitionSlotTable>;
export type NewAcquisitionSlot = Insertable<AcquisitionSlotTable>;
export type AcquisitionSlotUpdate = Updateable<AcquisitionSlotTable>;
export type AdminPasskeyRow = Selectable<AdminPasskeyTable>;
export type NewAdminPasskey = Insertable<AdminPasskeyTable>;
export type AdminPasskeyUpdate = Updateable<AdminPasskeyTable>;
export type AdminRecoveryCodeRow = Selectable<AdminRecoveryCodeTable>;
export type NewAdminRecoveryCode = Insertable<AdminRecoveryCodeTable>;
export type AdminRecoveryCodeUpdate = Updateable<AdminRecoveryCodeTable>;
export type AdminSessionRow = Selectable<AdminSessionTable>;
export type NewAdminSession = Insertable<AdminSessionTable>;
export type AdminSessionUpdate = Updateable<AdminSessionTable>;
@@ -1851,3 +1895,6 @@ export type TitleAliasUpdate = Updateable<TitleAliasTable>;
export type TrashItemRow = Selectable<TrashItemTable>;
export type NewTrashItem = Insertable<TrashItemTable>;
export type TrashItemUpdate = Updateable<TrashItemTable>;
export type WebauthnChallengeRow = Selectable<WebauthnChallengeTable>;
export type NewWebauthnChallenge = Insertable<WebauthnChallengeTable>;
export type WebauthnChallengeUpdate = Updateable<WebauthnChallengeTable>;