feat(x/core): real bridges + DI assembly for the turn runtime (stage 4)

Five bridges adapt existing app code to the runtime seams, each unit-
tested against injected fakes:

- RealAgentResolver: loadAgent + dynamic builders -> immutable
  ResolvedAgent. Model precedence override > agent > app default; tool
  descriptors from BuiltinTools (zod -> JSON schema) and MCP
  attachments (toolId schemes builtin:/mcp:server:tool); ask-human as
  the async requiresHuman tool. System prompt composed via
  composeSystemInstructions — extracted verbatim from streamAgent so
  old and new runtimes share one implementation — driven by the new
  opaque RequestedAgent.overrides.composition (session-sticky inputs
  preserve provider prefix caching; per-message context stays on
  UserMessage.userMessageContext as today).
- RealModelRegistry: models.json -> live AI SDK model; one streamText
  step (stopWhen: stepCountIs(1)) normalized into deltas, durable step
  events, and the completed assistant message.
- RealToolRegistry: execTool dispatch (builtins + MCP) with per-call
  abortRegistry bracketing and tool-output-stream -> durable progress.
- RealPermissionChecker: getToolPermissionMetadata rules (command
  allowlist, workspace file boundaries); session grants deferred.
- RealPermissionClassifier: classifyToolPermissions with conversation
  context now threaded through the classifier batch.

Interface refinements (doc updated): ToolExecutionContext gains
turnId/toolCallId; classifier takes a batch with turnId + messages;
TurnRuntime dep renamed bus -> lifecycleBus and SessionsImpl bus ->
sessionBus for strict-PROXY DI. Container registers the whole stack
(FS repos rooted at WorkDir/storage); the legacy agentRuntime
registration became lazy to survive the new import-order cycle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Ramnique Singh 2026-07-02 10:45:35 +05:30
parent ccf38c6a0f
commit 1d5782fa75
23 changed files with 1734 additions and 99 deletions

View file

@ -247,6 +247,12 @@ interface RequestedAgent {
agentId: string;
overrides?: {
model?: ModelDescriptor;
// Opaque composition hints interpreted by the agent resolver (e.g.
// work-dir id, voice/search/code modes). Persisted verbatim for audit.
// The resolver decides which keys affect system-prompt bytes; callers
// should keep prompt-affecting inputs session-sticky to preserve
// provider prefix caching across turns.
composition?: JsonValue;
};
}
```
@ -608,9 +614,17 @@ interface PermissionClassification {
reason: string;
}
interface PermissionClassificationBatch {
turnId: string;
// Conversation context: resolved context plus current-turn settled
// messages (the pending batch's tool results are not yet terminal).
messages: ConversationMessage[];
requests: PermissionClassificationInput[];
}
interface IPermissionClassifier {
classify(
requests: PermissionClassificationInput[],
batch: PermissionClassificationBatch,
signal: AbortSignal,
): Promise<PermissionClassification[]>;
}
@ -710,6 +724,8 @@ Sync and async execution are immutable tool metadata:
```ts
interface ToolExecutionContext {
turnId: string;
toolCallId: string;
signal: AbortSignal;
reportProgress(progress: JsonValue): Promise<void>;
}
@ -1125,7 +1141,7 @@ interface TurnRuntimeDependencies {
contextResolver: IContextResolver;
permissionChecker: IPermissionChecker;
permissionClassifier: IPermissionClassifier;
bus: IBus;
lifecycleBus: ITurnLifecycleBus;
}
class TurnRuntime implements ITurnRuntime {
@ -1139,7 +1155,7 @@ class TurnRuntime implements ITurnRuntime {
contextResolver,
permissionChecker,
permissionClassifier,
bus,
lifecycleBus,
}: TurnRuntimeDependencies);
}
```