bazis 0.99.2 → 0.99.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -327,14 +327,14 @@ function isSettlementResult(value: unknown): value is { readonly status: "record
327
327
  } catch { return false; }
328
328
  }
329
329
 
330
- type HookOutcome<T> = { readonly kind: "value"; readonly value: T } | { readonly kind: "timeout" } | { readonly kind: "cancelled" } | { readonly kind: "failed" };
330
+ type HookOutcome<T> = { readonly kind: "value"; readonly value: T } | { readonly kind: "timeout" } | { readonly kind: "cancelled" } | { readonly kind: "failed"; readonly error?: unknown };
331
331
 
332
332
  async function awaitHook<T>(operation: Promise<T>, signal: AbortSignal | undefined, timeoutMs: number | undefined): Promise<HookOutcome<T>> {
333
333
  // Observe both branches even after the caller's deadline wins: a late hook
334
334
  // rejection must never become an unhandled process-level rejection.
335
335
  const observed: Promise<HookOutcome<T>> = operation
336
336
  .then((value): HookOutcome<T> => ({ kind: "value", value }))
337
- .catch((): HookOutcome<T> => ({ kind: "failed" }));
337
+ .catch((error: unknown): HookOutcome<T> => ({ kind: "failed", error }));
338
338
  if (signal?.aborted) return { kind: "cancelled" };
339
339
  let timer: ReturnType<typeof setTimeout> | undefined;
340
340
  let detach: (() => void) | undefined;
@@ -646,6 +646,7 @@ export class AgentToolExecutor {
646
646
  tier: this.isPlatformHook(record) ? "platform" : "application", order: registration.order ?? 0,
647
647
  outcome: "failed",
648
648
  };
649
+ let hookError: unknown;
649
650
  const postEffect = state.executeStarted;
650
651
  const configuredTimeoutMs = registration.timeoutMs ?? 5_000;
651
652
  const remainingMs = !postEffect && state.deadlineUnixMs !== undefined
@@ -683,6 +684,7 @@ export class AgentToolExecutor {
683
684
  const activated = await awaitHook(record.activation.activateAsync(scope), controller.signal, boundedTimeoutMs);
684
685
  if (activated.kind !== "value") {
685
686
  if (activated.kind === "timeout") controller.abort();
687
+ if (activated.kind === "failed") hookError = activated.error;
686
688
  projection = { ...projection, outcome: activated.kind === "timeout" ? "timed-out" : activated.kind === "cancelled" ? (state.callerSignal?.aborted ? "cancelled" : "timed-out") : "failed" };
687
689
  terminal = this.errorResult(call, "TOOL_HOOK_SETTLEMENT_FAILED", "Agent Tool settlement handler was unavailable.", metadata, startedAt);
688
690
  } else {
@@ -695,6 +697,7 @@ export class AgentToolExecutor {
695
697
  const outcome = await awaitHook(Promise.resolve((handler.settle as (value: AgentToolSettlementEventV1, context: AgentToolHookContextV1) => unknown)(event, context)), controller.signal, invocationTimeoutMs);
696
698
  if (outcome.kind === "timeout") controller.abort();
697
699
  if (outcome.kind !== "value") {
700
+ if (outcome.kind === "failed") hookError = outcome.error;
698
701
  projection = { ...projection, outcome: outcome.kind === "timeout" ? "timed-out" : outcome.kind === "cancelled" ? (state.callerSignal?.aborted ? "cancelled" : "timed-out") : "failed" };
699
702
  terminal = this.errorResult(call, "TOOL_HOOK_SETTLEMENT_FAILED", "Agent Tool settlement failed.", metadata, startedAt);
700
703
  } else if (!isSettlementResult(outcome.value)) {
@@ -707,17 +710,20 @@ export class AgentToolExecutor {
707
710
  }
708
711
  }
709
712
  }
710
- } catch {
713
+ } catch (error) {
714
+ hookError = error;
711
715
  projection = { ...projection, outcome: "failed" };
712
716
  terminal = this.errorResult(call, "TOOL_HOOK_SETTLEMENT_FAILED", "Agent Tool settlement failed.", metadata, startedAt);
713
717
  } finally {
714
718
  detach?.();
715
719
  const disposeError = await this.disposeScope(scope, this.scopeDisposeTimeoutMs);
716
720
  if (disposeError !== undefined) {
721
+ hookError ??= disposeError;
717
722
  projection = { ...projection, outcome: "failed" };
718
723
  terminal = this.errorResult(call, "TOOL_HOOK_SETTLEMENT_FAILED", "Agent Tool settlement disposal failed.", metadata, startedAt);
719
724
  }
720
725
  }
726
+ this.logHookFailure(projection, call, event.agentName, hookError);
721
727
  const evidenceRemainingMs = postEffect || state.deadlineUnixMs === undefined
722
728
  ? registration.timeoutMs ?? 5_000
723
729
  : Math.max(0, Math.min(registration.timeoutMs ?? 5_000, state.deadlineUnixMs - Date.now()));
@@ -744,6 +750,7 @@ export class AgentToolExecutor {
744
750
  if (registration.kind !== "observer") continue;
745
751
  const scope = this.services.createScope();
746
752
  let outcome: AgentToolAuditHookProjectionV1["outcome"] = "failed";
753
+ let hookError: unknown;
747
754
  const timeoutMs = registration.timeoutMs ?? 5_000;
748
755
  const controller = new AbortController();
749
756
  const detach = attachAbortForwarding(options.signal, controller);
@@ -752,6 +759,7 @@ export class AgentToolExecutor {
752
759
  const activated = await awaitHook(record.activation.activateAsync(scope), controller.signal, timeoutMs);
753
760
  if (activated.kind !== "value") {
754
761
  if (activated.kind === "timeout") controller.abort();
762
+ if (activated.kind === "failed") hookError = activated.error;
755
763
  outcome = activated.kind === "timeout" ? "timed-out" : activated.kind === "cancelled" ? "cancelled" : "failed";
756
764
  } else {
757
765
  const handler = activated.value as { observe?: unknown };
@@ -764,15 +772,22 @@ export class AgentToolExecutor {
764
772
  Math.max(1, context.deadlineUnixMs - Date.now()),
765
773
  );
766
774
  if (observed.kind === "timeout" || observed.kind === "value") controller.abort();
775
+ if (observed.kind === "failed") hookError = observed.error;
767
776
  outcome = observed.kind === "value" ? "recorded" : observed.kind === "timeout" ? "timed-out" : observed.kind === "cancelled" ? "cancelled" : "failed";
768
777
  }
769
778
  }
770
- } catch {
779
+ } catch (error) {
780
+ hookError = error;
771
781
  outcome = controller.signal.aborted && options.signal?.aborted ? "cancelled" : "failed";
772
782
  } finally {
773
783
  detach?.();
774
- if (await this.disposeScope(scope, this.scopeDisposeTimeoutMs) !== undefined) outcome = "failed";
784
+ const disposeError = await this.disposeScope(scope, this.scopeDisposeTimeoutMs);
785
+ if (disposeError !== undefined) {
786
+ hookError ??= disposeError;
787
+ outcome = "failed";
788
+ }
775
789
  }
790
+ this.logHookFailure({ kind: registration.kind, id: registration.id, version: registration.version, outcome }, { id: settlement.callId, name: settlement.toolName }, settlement.agentName, hookError);
776
791
  await this.recordObserverHook(settlement, registration, record, outcome, timeoutMs);
777
792
  }
778
793
  }
@@ -796,7 +811,8 @@ export class AgentToolExecutor {
796
811
  });
797
812
  // Observer evidence is deliberately best effort and uses an independent
798
813
  // bounded write: it cannot alter the already-terminal Tool result.
799
- await awaitHook(Promise.resolve().then(() => auditSinkFunction(this.auditSink!)(entry)), undefined, timeoutMs);
814
+ const recorded = await awaitHook(Promise.resolve().then(() => auditSinkFunction(this.auditSink!)(entry)), undefined, timeoutMs);
815
+ if (recorded.kind !== "value") this.logAuditFailure("observer", settlement.toolName, settlement.callId, settlement.agentName, "best-effort", recorded);
800
816
  }
801
817
 
802
818
  private async executePipeline(
@@ -1086,6 +1102,7 @@ export class AgentToolExecutor {
1086
1102
  tier: isPlatform(record) ? "platform" : "application", order: registration.order ?? 0,
1087
1103
  outcome: "failed",
1088
1104
  };
1105
+ let hookError: unknown;
1089
1106
  const configuredTimeoutMs = registration.timeoutMs ?? 5_000;
1090
1107
  const remainingMs = executionState.deadlineUnixMs === undefined
1091
1108
  ? undefined : Math.max(0, executionState.deadlineUnixMs - Date.now());
@@ -1102,6 +1119,7 @@ export class AgentToolExecutor {
1102
1119
  const activated = await awaitHook(record.activation.activateAsync(scope), controller.signal, timeoutMs);
1103
1120
  if (activated.kind !== "value") {
1104
1121
  if (activated.kind === "timeout") controller.abort();
1122
+ if (activated.kind === "failed") hookError = activated.error;
1105
1123
  projection = { ...projection, outcome: activated.kind === "timeout" ? "timed-out" : activated.kind === "cancelled" ? "cancelled" : "failed" };
1106
1124
  terminal = this.errorResult(call, activated.kind === "timeout" ? "TOOL_HOOK_TIMEOUT" : activated.kind === "cancelled" ? "TOOL_HOOK_CANCELLED" : "TOOL_HOOK_UNAVAILABLE", "Agent Tool enforcement handler was unavailable.", metadata, startedAt);
1107
1125
  } else {
@@ -1113,27 +1131,31 @@ export class AgentToolExecutor {
1113
1131
  if (outcome.kind === "timeout") controller.abort();
1114
1132
  if (outcome.kind === "timeout") { projection = { ...projection, outcome: "timed-out" }; terminal = this.errorResult(call, "TOOL_HOOK_TIMEOUT", "Agent Tool enforcement hook timed out.", metadata, startedAt); }
1115
1133
  else if (outcome.kind === "cancelled") { projection = { ...projection, outcome: "cancelled" }; terminal = this.errorResult(call, "TOOL_HOOK_CANCELLED", "Agent Tool enforcement hook was cancelled.", metadata, startedAt); }
1116
- else if (outcome.kind === "failed") { terminal = this.errorResult(call, "TOOL_HOOK_FAILED", "Agent Tool enforcement hook failed.", metadata, startedAt); }
1134
+ else if (outcome.kind === "failed") { hookError = outcome.error; terminal = this.errorResult(call, "TOOL_HOOK_FAILED", "Agent Tool enforcement hook failed.", metadata, startedAt); }
1117
1135
  else if (!isHookDecision(outcome.value)) { projection = { ...projection, outcome: "invalid-result" }; terminal = this.errorResult(call, "TOOL_HOOK_DECISION_INVALID", "Agent Tool enforcement hook returned an invalid decision.", metadata, startedAt); }
1118
1136
  else {
1119
1137
  const decision = outcome.value;
1120
- projection = { ...projection, outcome: decision.decision === "allow" ? "allowed" : "denied", ...(decision.decision === "deny" && decision.reasonCode ? { reasonCode: decision.reasonCode } : {}), ...(decision.evidence ? { evidence: redactSensitive(normalizeHookEvidence(decision.evidence), this.hookRedaction) as JsonObject } : {}) };
1121
- if (decision.decision === "deny") terminal = this.deniedResult(call, "TOOL_HOOK_DENIED", "Tool execution was denied by an enforcement hook.", {}, metadata, startedAt);
1138
+ projection = { ...projection, outcome: decision.decision === "allow" ? "allowed" : "denied", ...(decision.decision === "deny" && decision.reasonCode ? { reasonCode: decision.reasonCode } : {}), ...(decision.decision === "deny" && decision.message ? { message: redactSensitive(decision.message) as string } : {}), ...(decision.evidence ? { evidence: redactSensitive(normalizeHookEvidence(decision.evidence), this.hookRedaction) as JsonObject } : {}) };
1139
+ // The hook's message is meant for the model, like a ToolError; reasonCode helps it branch.
1140
+ if (decision.decision === "deny") terminal = this.deniedResult(call, "TOOL_HOOK_DENIED", decision.message ?? "Tool execution was denied by an enforcement hook.", decision.reasonCode ? { reasonCode: decision.reasonCode } : {}, metadata, startedAt);
1122
1141
  }
1123
1142
  }
1124
1143
  }
1125
1144
  }
1126
- } catch {
1145
+ } catch (error) {
1146
+ hookError = error;
1127
1147
  projection = { ...projection, outcome: "failed" };
1128
1148
  terminal = this.errorResult(call, options.signal?.aborted ? "TOOL_HOOK_CANCELLED" : "TOOL_HOOK_FAILED", "Agent Tool enforcement hook failed.", metadata, startedAt);
1129
1149
  } finally {
1130
1150
  detach?.();
1131
1151
  const disposeError = await this.disposeScope(scope, this.scopeDisposeTimeoutMs);
1132
1152
  if (disposeError !== undefined) {
1153
+ hookError ??= disposeError;
1133
1154
  projection = { ...projection, outcome: "failed" };
1134
1155
  terminal = this.errorResult(call, "TOOL_HOOK_FAILED", "Agent Tool enforcement hook disposal failed.", metadata, startedAt);
1135
1156
  }
1136
1157
  }
1158
+ this.logHookFailure(projection, call, agentName, hookError);
1137
1159
  const evidenceRemainingMs = executionState.deadlineUnixMs === undefined
1138
1160
  ? registration.timeoutMs ?? 5_000
1139
1161
  : Math.max(0, Math.min(registration.timeoutMs ?? 5_000, executionState.deadlineUnixMs - Date.now()));
@@ -1174,8 +1196,12 @@ export class AgentToolExecutor {
1174
1196
  if (!this.auditSink) return true;
1175
1197
  try {
1176
1198
  const outcome = await awaitHook(Promise.resolve().then(() => auditSinkFunction(this.auditSink!)({ kind: "agent-tool", phase, callId: call.id, toolName: tool.metadata.name, input: redactSensitive(normalizeJsonValue(call.input, "hook.audit.input"), this.hookRedaction), tool: { name: tool.metadata.name, sideEffect: tool.metadata.sideEffect, approval: tool.metadata.approval, tags: tool.metadata.tags ?? [] }, startedAtUnixMs: startedAt, finishedAtUnixMs: Date.now(), durationMs: Date.now() - startedAt, metadata: redactSensitive(metadata, this.hookRedaction) as JsonObject, hook })), signal, timeoutMs);
1199
+ if (outcome.kind !== "value") this.logAuditFailure(phase, call.name, call.id, options.agentName, "required", outcome);
1177
1200
  return outcome.kind !== "value";
1178
- } catch { return true; }
1201
+ } catch (error) {
1202
+ this.logAuditFailure(phase, call.name, call.id, options.agentName, "required", { kind: "failed", error });
1203
+ return true;
1204
+ }
1179
1205
  }
1180
1206
 
1181
1207
  private frameworkTerminalAuditTimeout(remainingMs: number | undefined): number {
@@ -1195,8 +1221,12 @@ export class AgentToolExecutor {
1195
1221
  result: redactSensitive(result, this.hookRedaction) as AgentToolResult,
1196
1222
  startedAtUnixMs: startedAt, finishedAtUnixMs: Date.now(), durationMs: Date.now() - startedAt,
1197
1223
  })), signal, timeoutMs);
1224
+ if (outcome.kind !== "value") this.logAuditFailure("result", result.name, result.callId, undefined, "required", outcome);
1198
1225
  return outcome.kind !== "value";
1199
- } catch { return true; }
1226
+ } catch (error) {
1227
+ this.logAuditFailure("result", result.name, result.callId, undefined, "required", { kind: "failed", error });
1228
+ return true;
1229
+ }
1200
1230
  }
1201
1231
 
1202
1232
  private hasMandatoryHooks(): boolean {
@@ -1944,6 +1974,7 @@ export class AgentToolExecutor {
1944
1974
  (error: unknown) => ({ ok: false, error } as const),
1945
1975
  );
1946
1976
  const outcome = await awaitHook(settled, signal, timeoutMs);
1977
+ if (outcome.kind === "timeout") this.logAuditFailure(entry.phase, entry.toolName, entry.callId, entry.agentName, audit.failureMode, outcome);
1947
1978
  if (outcome.kind !== "value") return outcome;
1948
1979
  return outcome.value.ok
1949
1980
  ? { kind: "value", value: undefined }
@@ -1959,6 +1990,8 @@ export class AgentToolExecutor {
1959
1990
  originalResult: AgentToolResult | undefined,
1960
1991
  error: unknown,
1961
1992
  ): AgentToolResult | undefined {
1993
+ // Logged in both modes: in best-effort the call goes on, and the lost entry is visible only here.
1994
+ this.logAuditFailure(entry.phase, entry.toolName, entry.callId, entry.agentName, audit.failureMode, { kind: "failed", error });
1962
1995
  if (audit.failureMode === "best-effort") return undefined;
1963
1996
  const sideEffectingSuccess = entry.phase === "result"
1964
1997
  && originalResult?.status === "success"
@@ -1970,8 +2003,8 @@ export class AgentToolExecutor {
1970
2003
  call,
1971
2004
  outcomeUnknown ? "TOOL_AUDIT_FAILED_OUTCOME_UNKNOWN" : "TOOL_AUDIT_FAILED",
1972
2005
  outcomeUnknown
1973
- ? `Tool result audit failed after an outcome-unknown side-effecting operation; retry safety is unknown: ${errorMessageOf(error)}`
1974
- : `Tool audit failed: ${errorMessageOf(error)}`,
2006
+ ? "Tool result audit failed after an outcome-unknown side-effecting operation; retry safety is unknown."
2007
+ : "Tool audit failed.",
1975
2008
  metadata,
1976
2009
  startedAt,
1977
2010
  {
@@ -2021,12 +2054,12 @@ export class AgentToolExecutor {
2021
2054
  .then(async (result) => {
2022
2055
  const error = await this.disposeScope(scope, scopeDisposeTimeoutMs);
2023
2056
  if (error !== undefined) {
2024
- console.error("[agent-tool] deferred scope disposal failed:", redactSensitive(error));
2057
+ this.reportDeferred("Agent tool scope disposal failed", { tool: call.name, callId: call.id, error: redactSensitive(error) });
2025
2058
  }
2026
2059
  await onSettled(result);
2027
2060
  })
2028
2061
  .catch((error: unknown) => {
2029
- console.error("[agent-tool] deferred settlement handling failed:", redactSensitive(errorMessageOf(error)));
2062
+ this.reportDeferred("Agent tool settlement handling failed", { tool: call.name, callId: call.id, error: redactSensitive(error) });
2030
2063
  });
2031
2064
  }
2032
2065
 
@@ -2035,11 +2068,50 @@ export class AgentToolExecutor {
2035
2068
  try {
2036
2069
  await auditSinkFunction(audit.sink)(entry);
2037
2070
  } catch (error) {
2038
- console.error("[agent-tool] deferred settlement audit failed:", redactSensitive(errorMessageOf(error)));
2071
+ this.logAuditFailure(entry.phase, entry.toolName, entry.callId, entry.agentName, audit.failureMode, { kind: "failed", error });
2039
2072
  }
2040
2073
  }
2041
2074
 
2042
2075
 
2076
+ /**
2077
+ * The audit sink did not record an entry. `mode` is how the call treated it:
2078
+ * `fail-closed` stopped it, `best-effort` let it go on, `required` is hook evidence.
2079
+ */
2080
+ private logAuditFailure(
2081
+ phase: string,
2082
+ toolName: string,
2083
+ callId: string,
2084
+ agentName: string | undefined,
2085
+ mode: AgentToolAuditFailureMode | "required",
2086
+ outcome: HookOutcome<unknown>,
2087
+ ): void {
2088
+ if (outcome.kind === "value" || outcome.kind === "cancelled") return;
2089
+ this.logger?.error("Agent tool audit failed", {
2090
+ phase, mode, agent: agentName ?? null, tool: toolName, callId,
2091
+ ...(outcome.kind === "timeout" ? { reason: "timed out" } : outcome.error === undefined ? {} : { error: redactSensitive(outcome.error) }),
2092
+ });
2093
+ }
2094
+
2095
+ /** Late failures after the call returned; without an application logger they still reach stderr. */
2096
+ private reportDeferred(message: string, fields: Record<string, unknown>): void {
2097
+ if (this.logger) this.logger.error(message, fields);
2098
+ else console.error(`[agent-tool] ${message}`, fields);
2099
+ }
2100
+
2101
+ /** A hook that failed, timed out or answered nonsense; a cancelled run is not a failure. */
2102
+ private logHookFailure(
2103
+ hook: Pick<AgentToolAuditHookProjectionV1, "kind" | "id" | "version" | "outcome">,
2104
+ call: Pick<AgentToolCall, "id" | "name">,
2105
+ agentName: string | undefined,
2106
+ error: unknown,
2107
+ ): void {
2108
+ if (hook.outcome !== "failed" && hook.outcome !== "timed-out" && hook.outcome !== "invalid-result") return;
2109
+ this.logger?.error("Agent tool hook failed", {
2110
+ hook: `${hook.kind}:${hook.id}@${hook.version}`, outcome: hook.outcome, agent: agentName ?? null, tool: call.name, callId: call.id,
2111
+ ...(error === undefined ? {} : { error: redactSensitive(error) }),
2112
+ });
2113
+ }
2114
+
2043
2115
  /**
2044
2116
  * A tool's own failure: a ToolError message reaches the model as written;
2045
2117
  * anything else is replaced by `fallback` and logged with its stack.
@@ -36,4 +36,4 @@ export interface AgentToolSettlementResultV1 { readonly status: "recorded"; read
36
36
  export interface AgentToolSettlementHookV1 { settle(event: AgentToolSettlementEventV1, context: AgentToolHookContextV1): AgentToolSettlementResultV1 | Promise<AgentToolSettlementResultV1>; }
37
37
  export interface AgentToolObserverEventV1 { readonly type: "agent-tool.observer/v1"; readonly settlement: AgentToolSettlementEventV1; }
38
38
  export interface AgentToolObserverHookV1 { observe(event: AgentToolObserverEventV1, context: AgentToolHookContextV1): void | Promise<void>; }
39
- export interface AgentToolAuditHookProjectionV1 extends AgentToolHookRefV1 { readonly tier: "platform" | "application"; readonly order: number; readonly outcome: "allowed" | "denied" | "recorded" | "failed" | "timed-out" | "cancelled" | "invalid-result"; readonly reasonCode?: string; readonly evidence?: JsonObject; }
39
+ export interface AgentToolAuditHookProjectionV1 extends AgentToolHookRefV1 { readonly tier: "platform" | "application"; readonly order: number; readonly outcome: "allowed" | "denied" | "recorded" | "failed" | "timed-out" | "cancelled" | "invalid-result"; readonly reasonCode?: string; readonly message?: string; readonly evidence?: JsonObject; }
@@ -108,6 +108,17 @@ details stay out of the LLM — and goes to the application log with its stack
108
108
  call failed`. The LLM connectors pass the application logger; a runtime created
109
109
  by hand takes `options.logger`.
110
110
 
111
+ An enforcement hook that denies a call explains why with `message` — it reaches
112
+ the model as written, with `reasonCode` in `error.details`, and both go to the
113
+ audit. A hook that throws, times out or returns an invalid answer is logged as
114
+ `Agent tool hook failed` with the error. `llmConnect` and `llmRouter` build
115
+ `AgentRuntime` at start, so wrong runtime options or enforcement hooks without
116
+ an audit sink stop the application with a configuration error.
117
+
118
+ An audit sink that fails is logged as `Agent tool audit failed` with the
119
+ phase and mode, in `best-effort` too, where the call goes on; the model sees
120
+ only `Tool audit failed.`.
121
+
111
122
  Retries are opt-in. `write` and `external` tools need an idempotency key before
112
123
  the executor repeats them. Runtime-generated keys include both the invocation id
113
124
  and the unique model call id; `toolExecution.idempotencyKeyForCall` gives a
@@ -232,7 +232,7 @@ export class CodexAppServerClient implements CodexClient {
232
232
  if (method === "account/updated") this.modelCache = undefined;
233
233
  }, () => { this.clearLogin(); this.modelCache = undefined; });
234
234
  const initialized = await client.request("initialize", {
235
- clientInfo: { name: "bazis", title: "Bazis", version: "0.99.2" }, capabilities: { experimentalApi: true },
235
+ clientInfo: { name: "bazis", title: "Bazis", version: "0.99.4" }, capabilities: { experimentalApi: true },
236
236
  }, 15_000, signal);
237
237
  if (resolve(text(initialized.codexHome, 4096)) !== this.paths.home) throw new CodexError("PROTOCOL_ERROR");
238
238
  client.notify("initialized");
@@ -1,6 +1,6 @@
1
1
  import type { AppConfig, ConfigRegistry, Secret } from "../../kernel";
2
2
  import { AGENT_MODEL_PROVIDER, AGENT_RUNTIME_OPTIONS, AgentRuntime, type AgentModelProvider } from "../../agent";
3
- import { DI, SERVICE_PROVIDER, type ProviderDefinition } from "../../di";
3
+ import { DI, DiError, HOSTED_SERVICE, SERVICE_PROVIDER, type ProviderDefinition } from "../../di";
4
4
  import { LOGGER } from "../../kernel/logging/Logger";
5
5
  import { reader, requireValue } from "../connectorConfig";
6
6
  import { InfraError, type InfraConnector } from "../InfraConnector";
@@ -66,6 +66,19 @@ export const agentRuntimeProviders: readonly ProviderDefinition[] = Object.freez
66
66
  const options = services.has(AGENT_RUNTIME_OPTIONS) ? services.resolve(AGENT_RUNTIME_OPTIONS) : {};
67
67
  return new AgentRuntime(services, model, { ...options, logger: options.logger ?? services.tryResolve(LOGGER) });
68
68
  })),
69
+ // Builds the runtime at start: wrong options or hooks without an audit sink
70
+ // stop the application instead of failing every agent request.
71
+ DI.singleton(DI.factoryProviderWithResolver(HOSTED_SERVICE, [], (resolver) => ({
72
+ start() {
73
+ try {
74
+ resolver.resolve(AgentRuntime);
75
+ } catch (error) {
76
+ if (error instanceof DiError) throw error;
77
+ throw new DiError(`AgentRuntime: ${error instanceof Error ? error.message : String(error)}`);
78
+ }
79
+ },
80
+ stop() {},
81
+ }))),
69
82
  ]);
70
83
 
71
84
  export function llmConnect<T extends LlmConfigShape>(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bazis",
3
- "version": "0.99.2",
3
+ "version": "0.99.4",
4
4
  "description": "Modular backend framework for Bun in TypeScript: DI wired by codegen, HTTP, a PostgreSQL ORM, JWT, WebSocket, agents and a CLI.",
5
5
  "license": "MIT",
6
6
  "author": "Alexander Alenin",