omk-agent-core 0.98.0 → 0.98.1

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.
Files changed (49) hide show
  1. package/dist/harness/agent-harness.d.ts +9 -8
  2. package/dist/harness/agent-harness.d.ts.map +1 -1
  3. package/dist/harness/agent-harness.js +145 -182
  4. package/dist/harness/agent-harness.js.map +1 -1
  5. package/dist/harness/compaction/branch-summarization.d.ts +5 -1
  6. package/dist/harness/compaction/branch-summarization.d.ts.map +1 -1
  7. package/dist/harness/compaction/branch-summarization.js +3 -3
  8. package/dist/harness/compaction/branch-summarization.js.map +1 -1
  9. package/dist/harness/compaction/compaction.d.ts +8 -4
  10. package/dist/harness/compaction/compaction.d.ts.map +1 -1
  11. package/dist/harness/compaction/compaction.js +12 -97
  12. package/dist/harness/compaction/compaction.js.map +1 -1
  13. package/dist/harness/compaction/operation.d.ts +6 -0
  14. package/dist/harness/compaction/operation.d.ts.map +1 -0
  15. package/dist/harness/compaction/operation.js +2 -0
  16. package/dist/harness/compaction/operation.js.map +1 -0
  17. package/dist/harness/compaction/summarization-prompts.d.ts +5 -0
  18. package/dist/harness/compaction/summarization-prompts.d.ts.map +1 -0
  19. package/dist/harness/compaction/summarization-prompts.js +88 -0
  20. package/dist/harness/compaction/summarization-prompts.js.map +1 -0
  21. package/dist/harness/errors.d.ts +15 -0
  22. package/dist/harness/errors.d.ts.map +1 -0
  23. package/dist/harness/errors.js +32 -0
  24. package/dist/harness/errors.js.map +1 -0
  25. package/dist/harness/harness-session.d.ts +105 -0
  26. package/dist/harness/harness-session.d.ts.map +1 -0
  27. package/dist/harness/harness-session.js +116 -0
  28. package/dist/harness/harness-session.js.map +1 -0
  29. package/dist/harness/messages.d.ts +3 -1
  30. package/dist/harness/messages.d.ts.map +1 -1
  31. package/dist/harness/messages.js +26 -0
  32. package/dist/harness/messages.js.map +1 -1
  33. package/dist/harness/name-validation.d.ts +2 -0
  34. package/dist/harness/name-validation.d.ts.map +1 -0
  35. package/dist/harness/name-validation.js +11 -0
  36. package/dist/harness/name-validation.js.map +1 -0
  37. package/dist/harness/stream-options.d.ts +31 -0
  38. package/dist/harness/stream-options.d.ts.map +1 -0
  39. package/dist/harness/stream-options.js +66 -0
  40. package/dist/harness/stream-options.js.map +1 -0
  41. package/dist/harness/summarization-retry.d.ts +29 -0
  42. package/dist/harness/summarization-retry.d.ts.map +1 -0
  43. package/dist/harness/summarization-retry.js +20 -0
  44. package/dist/harness/summarization-retry.js.map +1 -0
  45. package/dist/harness/types.d.ts +36 -41
  46. package/dist/harness/types.d.ts.map +1 -1
  47. package/dist/harness/types.js +1 -32
  48. package/dist/harness/types.js.map +1 -1
  49. package/package.json +2 -2
@@ -1,9 +1,10 @@
1
1
  import { type AssistantMessage, type ImageContent, type Model } from "omk-ai";
2
2
  import type { AgentMessage, AgentTool, QueueMode, ThinkingLevel } from "../types.ts";
3
- import type { AbortResult, AgentHarnessEvent, AgentHarnessEventResultMap, AgentHarnessOptions, AgentHarnessOwnEvent, AgentHarnessResources, AgentHarnessStreamOptions, ExecutionEnv, NavigateTreeResult, PromptTemplate, Skill } from "./types.ts";
3
+ import type { AbortResult, AgentHarnessEvent, AgentHarnessEventResultMap, AgentHarnessOptions, AgentHarnessOwnEvent, AgentHarnessResources, AgentHarnessStreamOptions, CompactResult, ExecutionEnv, HarnessSession, NavigateTreeResult, PromptTemplate, Skill } from "./types.ts";
4
4
  export declare class AgentHarness<TSkill extends Skill = Skill, TPromptTemplate extends PromptTemplate = PromptTemplate, TTool extends AgentTool = AgentTool> {
5
5
  readonly env: ExecutionEnv;
6
6
  private session;
7
+ private readonly sessionFacade;
7
8
  private phase;
8
9
  private runAbortController?;
9
10
  private runPromise?;
@@ -12,6 +13,7 @@ export declare class AgentHarness<TSkill extends Skill = Skill, TPromptTemplate
12
13
  private thinkingLevel;
13
14
  private systemPrompt;
14
15
  private streamOptions;
16
+ private compactionSettings;
15
17
  private getApiKeyAndHeaders?;
16
18
  private resources;
17
19
  private tools;
@@ -23,7 +25,6 @@ export declare class AgentHarness<TSkill extends Skill = Skill, TPromptTemplate
23
25
  private nextTurnQueue;
24
26
  private handlers;
25
27
  constructor(options: AgentHarnessOptions<TSkill, TPromptTemplate, TTool>);
26
- private getHandlers;
27
28
  private emitOwn;
28
29
  private emitAny;
29
30
  private emitHook;
@@ -42,6 +43,8 @@ export declare class AgentHarness<TSkill extends Skill = Skill, TPromptTemplate
42
43
  private handleAgentEvent;
43
44
  private emitRunFailure;
44
45
  private executeTurn;
46
+ private executeAgentRun;
47
+ private recoverContextOverflow;
45
48
  prompt(text: string, options?: {
46
49
  images?: ImageContent[];
47
50
  }): Promise<AssistantMessage>;
@@ -56,13 +59,11 @@ export declare class AgentHarness<TSkill extends Skill = Skill, TPromptTemplate
56
59
  nextTurn(text: string, options?: {
57
60
  images?: ImageContent[];
58
61
  }): Promise<void>;
62
+ getSession(): HarnessSession;
59
63
  appendMessage(message: AgentMessage): Promise<void>;
60
- compact(customInstructions?: string): Promise<{
61
- summary: string;
62
- firstKeptEntryId: string;
63
- tokensBefore: number;
64
- details?: unknown;
65
- }>;
64
+ private runCompaction;
65
+ private maybeAutoCompact;
66
+ compact(customInstructions?: string): Promise<CompactResult>;
66
67
  navigateTree(targetId: string, options?: {
67
68
  summarize?: boolean;
68
69
  customInstructions?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"agent-harness.d.ts","sourceRoot":"","sources":["../../src/harness/agent-harness.ts"],"names":[],"mappings":"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","sourcesContent":["import { type AssistantMessage, type ImageContent, type Model, streamSimple, type UserMessage } from \"omk-ai\";\nimport { runAgentLoop } from \"../agent-loop.ts\";\nimport type {\n\tAgentContext,\n\tAgentEvent,\n\tAgentLoopConfig,\n\tAgentMessage,\n\tAgentTool,\n\tQueueMode,\n\tStreamFn,\n\tThinkingLevel,\n} from \"../types.ts\";\nimport { collectEntriesForBranchSummary, generateBranchSummary } from \"./compaction/branch-summarization.ts\";\nimport { compact, DEFAULT_COMPACTION_SETTINGS, prepareCompaction } from \"./compaction/compaction.ts\";\nimport { convertToLlm } from \"./messages.ts\";\nimport { formatPromptTemplateInvocation } from \"./prompt-templates.ts\";\nimport { formatSkillInvocation } from \"./skills.ts\";\nimport type {\n\tAbortResult,\n\tAgentHarnessEvent,\n\tAgentHarnessEventResultMap,\n\tAgentHarnessOptions,\n\tAgentHarnessOwnEvent,\n\tAgentHarnessPhase,\n\tAgentHarnessResources,\n\tAgentHarnessStreamOptions,\n\tAgentHarnessStreamOptionsPatch,\n\tExecutionEnv,\n\tNavigateTreeResult,\n\tPendingSessionWrite,\n\tPromptTemplate,\n\tSession,\n\tSkill,\n} from \"./types.ts\";\nimport { AgentHarnessError, BranchSummaryError, CompactionError, SessionError, toError } from \"./types.ts\";\n\nfunction createUserMessage(text: string, images?: ImageContent[]): UserMessage {\n\tconst content: Array<{ type: \"text\"; text: string } | ImageContent> = [{ type: \"text\", text }];\n\tif (images) content.push(...images);\n\treturn { role: \"user\", content, timestamp: Date.now() };\n}\n\nfunction createFailureMessage(model: Model<any>, error: unknown, aborted: boolean): AssistantMessage {\n\treturn {\n\t\trole: \"assistant\",\n\t\tcontent: [{ type: \"text\", text: \"\" }],\n\t\tapi: model.api,\n\t\tprovider: model.provider,\n\t\tmodel: model.id,\n\t\tstopReason: aborted ? \"aborted\" : \"error\",\n\t\terrorMessage: error instanceof Error ? error.message : String(error),\n\t\ttimestamp: Date.now(),\n\t\tusage: {\n\t\t\tinput: 0,\n\t\t\toutput: 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\ttotalTokens: 0,\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t},\n\t};\n}\n\nfunction cloneStreamOptions(streamOptions?: AgentHarnessStreamOptions): AgentHarnessStreamOptions {\n\treturn {\n\t\t...streamOptions,\n\t\theaders: streamOptions?.headers ? { ...streamOptions.headers } : undefined,\n\t\tmetadata: streamOptions?.metadata ? { ...streamOptions.metadata } : undefined,\n\t};\n}\n\nfunction mergeHeaders(...headers: Array<Record<string, string> | undefined>): Record<string, string> | undefined {\n\tconst merged: Record<string, string> = {};\n\tlet hasHeaders = false;\n\tfor (const entry of headers) {\n\t\tif (!entry) continue;\n\t\tObject.assign(merged, entry);\n\t\thasHeaders = true;\n\t}\n\treturn hasHeaders ? merged : undefined;\n}\n\nfunction findDuplicateNames(names: string[]): string[] {\n\tconst seen = new Set<string>();\n\tconst duplicates = new Set<string>();\n\tfor (const name of names) {\n\t\tif (seen.has(name)) duplicates.add(name);\n\t\tseen.add(name);\n\t}\n\treturn [...duplicates];\n}\n\nfunction applyStreamOptionsPatch(\n\tbase: AgentHarnessStreamOptions,\n\tpatch?: AgentHarnessStreamOptionsPatch,\n): AgentHarnessStreamOptions {\n\tconst result = cloneStreamOptions(base);\n\tif (!patch) return result;\n\n\tif (Object.hasOwn(patch, \"transport\")) result.transport = patch.transport;\n\tif (Object.hasOwn(patch, \"timeoutMs\")) result.timeoutMs = patch.timeoutMs;\n\tif (Object.hasOwn(patch, \"maxRetries\")) result.maxRetries = patch.maxRetries;\n\tif (Object.hasOwn(patch, \"maxRetryDelayMs\")) result.maxRetryDelayMs = patch.maxRetryDelayMs;\n\tif (Object.hasOwn(patch, \"cacheRetention\")) result.cacheRetention = patch.cacheRetention;\n\n\tif (Object.hasOwn(patch, \"headers\")) {\n\t\tif (patch.headers === undefined) {\n\t\t\tresult.headers = undefined;\n\t\t} else {\n\t\t\tconst headers = { ...(result.headers ?? {}) };\n\t\t\tfor (const [key, value] of Object.entries(patch.headers)) {\n\t\t\t\tif (value === undefined) delete headers[key];\n\t\t\t\telse headers[key] = value;\n\t\t\t}\n\t\t\tresult.headers = Object.keys(headers).length > 0 ? headers : undefined;\n\t\t}\n\t}\n\n\tif (Object.hasOwn(patch, \"metadata\")) {\n\t\tif (patch.metadata === undefined) {\n\t\t\tresult.metadata = undefined;\n\t\t} else {\n\t\t\tconst metadata = { ...(result.metadata ?? {}) };\n\t\t\tfor (const [key, value] of Object.entries(patch.metadata)) {\n\t\t\t\tif (value === undefined) delete metadata[key];\n\t\t\t\telse metadata[key] = value;\n\t\t\t}\n\t\t\tresult.metadata = Object.keys(metadata).length > 0 ? metadata : undefined;\n\t\t}\n\t}\n\n\treturn result;\n}\n\nconst SUBSCRIBER_EVENT_TYPE = \"*\";\n\ntype AgentHarnessHandler = (event: any, signal?: AbortSignal) => Promise<any> | any;\n\nfunction normalizeHarnessError(error: unknown, fallbackCode: AgentHarnessError[\"code\"]): AgentHarnessError {\n\tif (error instanceof AgentHarnessError) return error;\n\tconst cause = toError(error);\n\tif (cause instanceof SessionError) return new AgentHarnessError(\"session\", cause.message, cause);\n\tif (cause instanceof CompactionError) return new AgentHarnessError(\"compaction\", cause.message, cause);\n\tif (cause instanceof BranchSummaryError) return new AgentHarnessError(\"branch_summary\", cause.message, cause);\n\treturn new AgentHarnessError(fallbackCode, cause.message, cause);\n}\n\nfunction normalizeHookError(error: unknown): AgentHarnessError {\n\treturn normalizeHarnessError(error, \"hook\");\n}\n\ninterface AgentHarnessTurnState<\n\tTSkill extends Skill = Skill,\n\tTPromptTemplate extends PromptTemplate = PromptTemplate,\n\tTTool extends AgentTool = AgentTool,\n> {\n\tmessages: AgentMessage[];\n\tresources: AgentHarnessResources<TSkill, TPromptTemplate>;\n\tstreamOptions: AgentHarnessStreamOptions;\n\tsessionId: string;\n\tsystemPrompt: string;\n\tmodel: Model<any>;\n\tthinkingLevel: ThinkingLevel;\n\ttools: TTool[];\n\tactiveTools: TTool[];\n}\n\nexport class AgentHarness<\n\tTSkill extends Skill = Skill,\n\tTPromptTemplate extends PromptTemplate = PromptTemplate,\n\tTTool extends AgentTool = AgentTool,\n> {\n\treadonly env: ExecutionEnv;\n\tprivate session: Session;\n\tprivate phase: AgentHarnessPhase = \"idle\";\n\tprivate runAbortController?: AbortController;\n\tprivate runPromise?: Promise<void>;\n\tprivate pendingSessionWrites: PendingSessionWrite[] = [];\n\tprivate model: Model<any>;\n\tprivate thinkingLevel: ThinkingLevel;\n\tprivate systemPrompt: AgentHarnessOptions<TSkill, TPromptTemplate, TTool>[\"systemPrompt\"];\n\tprivate streamOptions: AgentHarnessStreamOptions;\n\tprivate getApiKeyAndHeaders?: AgentHarnessOptions[\"getApiKeyAndHeaders\"];\n\tprivate resources: AgentHarnessResources<TSkill, TPromptTemplate>;\n\tprivate tools = new Map<string, TTool>();\n\tprivate activeToolNames: string[];\n\tprivate steerQueue: UserMessage[] = [];\n\tprivate steeringQueueMode: QueueMode;\n\tprivate followUpQueue: UserMessage[] = [];\n\tprivate followUpQueueMode: QueueMode;\n\tprivate nextTurnQueue: AgentMessage[] = [];\n\tprivate handlers = new Map<string, Set<AgentHarnessHandler>>();\n\n\tconstructor(options: AgentHarnessOptions<TSkill, TPromptTemplate, TTool>) {\n\t\tthis.env = options.env;\n\t\tthis.session = options.session;\n\t\tthis.resources = options.resources ?? {};\n\t\tthis.streamOptions = cloneStreamOptions(options.streamOptions);\n\t\tthis.systemPrompt = options.systemPrompt;\n\t\tthis.getApiKeyAndHeaders = options.getApiKeyAndHeaders;\n\t\tthis.validateUniqueNames(\n\t\t\t(options.tools ?? []).map((tool) => tool.name),\n\t\t\t\"Duplicate tool name(s)\",\n\t\t);\n\t\tfor (const tool of options.tools ?? []) {\n\t\t\tthis.tools.set(tool.name, tool);\n\t\t}\n\t\tthis.model = options.model;\n\t\tthis.thinkingLevel = options.thinkingLevel ?? \"off\";\n\t\tthis.activeToolNames = options.activeToolNames\n\t\t\t? [...options.activeToolNames]\n\t\t\t: (options.tools ?? []).map((tool) => tool.name);\n\t\tthis.validateUniqueNames(this.activeToolNames, \"Duplicate active tool name(s)\");\n\t\tthis.validateToolNames(this.activeToolNames);\n\t\tthis.steeringQueueMode = options.steeringMode ?? \"one-at-a-time\";\n\t\tthis.followUpQueueMode = options.followUpMode ?? \"one-at-a-time\";\n\t}\n\n\tprivate getHandlers(type: string): Set<AgentHarnessHandler> | undefined {\n\t\treturn this.handlers.get(type);\n\t}\n\n\tprivate async emitOwn(event: AgentHarnessOwnEvent<TSkill, TPromptTemplate>, signal?: AbortSignal): Promise<void> {\n\t\tfor (const listener of this.getHandlers(SUBSCRIBER_EVENT_TYPE) ?? []) {\n\t\t\ttry {\n\t\t\t\tawait listener(event, signal);\n\t\t\t} catch (error) {\n\t\t\t\tthrow normalizeHookError(error);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async emitAny(event: AgentHarnessEvent<TSkill, TPromptTemplate>, signal?: AbortSignal): Promise<void> {\n\t\tfor (const listener of this.getHandlers(SUBSCRIBER_EVENT_TYPE) ?? []) {\n\t\t\ttry {\n\t\t\t\tawait listener(event, signal);\n\t\t\t} catch (error) {\n\t\t\t\tthrow normalizeHookError(error);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async emitHook<TType extends keyof AgentHarnessEventResultMap>(\n\t\tevent: Extract<AgentHarnessOwnEvent, { type: TType }>,\n\t): Promise<AgentHarnessEventResultMap[TType] | undefined> {\n\t\tconst handlers = this.getHandlers(event.type as TType);\n\t\tif (!handlers || handlers.size === 0) return undefined;\n\t\tlet lastResult: AgentHarnessEventResultMap[TType] | undefined;\n\t\tfor (const handler of handlers) {\n\t\t\ttry {\n\t\t\t\tconst result = await handler(event);\n\t\t\t\tif (result !== undefined) {\n\t\t\t\t\tlastResult = result;\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tthrow normalizeHookError(error);\n\t\t\t}\n\t\t}\n\t\treturn lastResult;\n\t}\n\n\tprivate async emitBeforeProviderRequest(\n\t\tmodel: Model<any>,\n\t\tsessionId: string,\n\t\tstreamOptions: AgentHarnessStreamOptions,\n\t): Promise<AgentHarnessStreamOptions> {\n\t\tconst handlers = this.getHandlers(\"before_provider_request\");\n\t\tlet current = cloneStreamOptions(streamOptions);\n\t\tif (!handlers || handlers.size === 0) return current;\n\t\tfor (const handler of handlers) {\n\t\t\ttry {\n\t\t\t\tconst result = await handler({\n\t\t\t\t\ttype: \"before_provider_request\",\n\t\t\t\t\tmodel,\n\t\t\t\t\tsessionId,\n\t\t\t\t\tstreamOptions: cloneStreamOptions(current),\n\t\t\t\t});\n\t\t\t\tif (result?.streamOptions) {\n\t\t\t\t\tcurrent = applyStreamOptionsPatch(current, result.streamOptions);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tthrow normalizeHookError(error);\n\t\t\t}\n\t\t}\n\t\treturn current;\n\t}\n\n\tprivate async emitBeforeProviderPayload(model: Model<any>, payload: unknown): Promise<unknown> {\n\t\tconst handlers = this.getHandlers(\"before_provider_payload\");\n\t\tlet current = payload;\n\t\tif (!handlers || handlers.size === 0) return current;\n\t\tfor (const handler of handlers) {\n\t\t\ttry {\n\t\t\t\tconst result = await handler({ type: \"before_provider_payload\", model, payload: current });\n\t\t\t\tif (result !== undefined) {\n\t\t\t\t\tcurrent = result.payload;\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tthrow normalizeHookError(error);\n\t\t\t}\n\t\t}\n\t\treturn current;\n\t}\n\n\tprivate async emitQueueUpdate(): Promise<void> {\n\t\tawait this.emitOwn({\n\t\t\ttype: \"queue_update\",\n\t\t\tsteer: [...this.steerQueue],\n\t\t\tfollowUp: [...this.followUpQueue],\n\t\t\tnextTurn: [...this.nextTurnQueue],\n\t\t});\n\t}\n\n\tprivate startRunPromise(): { runPromise: Promise<void>; finishRunPromise: () => void } {\n\t\tlet finish = () => {};\n\t\tconst runPromise = new Promise<void>((resolve) => {\n\t\t\tfinish = resolve;\n\t\t});\n\t\tthis.runPromise = runPromise;\n\t\treturn {\n\t\t\trunPromise,\n\t\t\tfinishRunPromise: () => {\n\t\t\t\t// A settled/agent_end listener may have started a new run while this\n\t\t\t\t// one was still unwinding; only clear state still owned by this run.\n\t\t\t\tif (this.runPromise === runPromise) {\n\t\t\t\t\tthis.runPromise = undefined;\n\t\t\t\t}\n\t\t\t\tfinish();\n\t\t\t},\n\t\t};\n\t}\n\n\tprivate async createTurnState(): Promise<AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>> {\n\t\tconst context = await this.session.buildContext();\n\t\tconst resources = this.getResources();\n\t\tconst sessionMetadata = await this.session.getMetadata();\n\t\tconst tools = [...this.tools.values()];\n\t\tconst activeTools = this.activeToolNames\n\t\t\t.map((name) => this.tools.get(name))\n\t\t\t.filter((tool): tool is TTool => tool !== undefined);\n\t\tlet systemPrompt = \"You are a helpful assistant.\";\n\t\tif (typeof this.systemPrompt === \"string\") {\n\t\t\tsystemPrompt = this.systemPrompt;\n\t\t} else if (this.systemPrompt) {\n\t\t\tsystemPrompt = await this.systemPrompt({\n\t\t\t\tenv: this.env,\n\t\t\t\tsession: this.session,\n\t\t\t\tmodel: this.model,\n\t\t\t\tthinkingLevel: this.thinkingLevel,\n\t\t\t\tactiveTools,\n\t\t\t\tresources,\n\t\t\t});\n\t\t}\n\t\treturn {\n\t\t\tmessages: context.messages,\n\t\t\tresources,\n\t\t\tstreamOptions: cloneStreamOptions(this.streamOptions),\n\t\t\tsessionId: sessionMetadata.id,\n\t\t\tsystemPrompt,\n\t\t\tmodel: this.model,\n\t\t\tthinkingLevel: this.thinkingLevel,\n\t\t\ttools,\n\t\t\tactiveTools,\n\t\t};\n\t}\n\n\tprivate createContext(\n\t\tturnState: AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>,\n\t\tsystemPrompt?: string,\n\t): AgentContext {\n\t\treturn {\n\t\t\tsystemPrompt: systemPrompt ?? turnState.systemPrompt,\n\t\t\tmessages: turnState.messages.slice(),\n\t\t\ttools: turnState.activeTools.slice(),\n\t\t};\n\t}\n\n\tprivate createStreamFn(getTurnState: () => AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>): StreamFn {\n\t\treturn async (model, context, streamOptions) => {\n\t\t\tconst turnState = getTurnState();\n\t\t\tconst auth = await this.getApiKeyAndHeaders?.(model);\n\t\t\tconst snapshotOptions: AgentHarnessStreamOptions = {\n\t\t\t\t...turnState.streamOptions,\n\t\t\t\theaders: mergeHeaders(turnState.streamOptions.headers, auth?.headers),\n\t\t\t};\n\t\t\tconst requestOptions = await this.emitBeforeProviderRequest(model, turnState.sessionId, snapshotOptions);\n\t\t\treturn streamSimple(model, context, {\n\t\t\t\tcacheRetention: requestOptions.cacheRetention,\n\t\t\t\theaders: requestOptions.headers,\n\t\t\t\tmaxRetries: requestOptions.maxRetries,\n\t\t\t\tmaxRetryDelayMs: requestOptions.maxRetryDelayMs,\n\t\t\t\tmetadata: requestOptions.metadata,\n\t\t\t\tonPayload: async (payload) => await this.emitBeforeProviderPayload(model, payload),\n\t\t\t\tonResponse: async (response) => {\n\t\t\t\t\tconst headers = { ...(response.headers as Record<string, string>) };\n\t\t\t\t\tawait this.emitOwn(\n\t\t\t\t\t\t{ type: \"after_provider_response\", status: response.status, headers },\n\t\t\t\t\t\tstreamOptions?.signal,\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t\treasoning: streamOptions?.reasoning,\n\t\t\t\tsignal: streamOptions?.signal,\n\t\t\t\tsessionId: turnState.sessionId,\n\t\t\t\ttimeoutMs: requestOptions.timeoutMs,\n\t\t\t\ttransport: requestOptions.transport,\n\t\t\t\tapiKey: auth?.apiKey,\n\t\t\t});\n\t\t};\n\t}\n\n\tprivate async drainQueuedMessages(queue: AgentMessage[], mode: QueueMode): Promise<AgentMessage[]> {\n\t\tconst messages = mode === \"all\" ? queue.splice(0) : queue.splice(0, 1);\n\t\tif (messages.length === 0) return messages;\n\t\ttry {\n\t\t\tawait this.emitQueueUpdate();\n\t\t\treturn messages;\n\t\t} catch (error) {\n\t\t\tqueue.unshift(...messages);\n\t\t\tthrow normalizeHookError(error);\n\t\t}\n\t}\n\n\tprivate createLoopConfig(\n\t\tgetTurnState: () => AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>,\n\t\tsetTurnState: (turnState: AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>) => void,\n\t): AgentLoopConfig {\n\t\tconst turnState = getTurnState();\n\t\treturn {\n\t\t\tmodel: turnState.model,\n\t\t\treasoning: turnState.thinkingLevel === \"off\" ? undefined : turnState.thinkingLevel,\n\t\t\tconvertToLlm,\n\t\t\ttransformContext: async (messages) => {\n\t\t\t\tconst result = await this.emitHook({ type: \"context\", messages: [...messages] });\n\t\t\t\treturn result?.messages ?? messages;\n\t\t\t},\n\t\t\tbeforeToolCall: async ({ toolCall, args }) => {\n\t\t\t\tconst result = await this.emitHook({\n\t\t\t\t\ttype: \"tool_call\",\n\t\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\t\ttoolName: toolCall.name,\n\t\t\t\t\tinput: args as Record<string, unknown>,\n\t\t\t\t});\n\t\t\t\treturn result ? { block: result.block, reason: result.reason } : undefined;\n\t\t\t},\n\t\t\tafterToolCall: async ({ toolCall, args, result, isError }) => {\n\t\t\t\tconst patch = await this.emitHook({\n\t\t\t\t\ttype: \"tool_result\",\n\t\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\t\ttoolName: toolCall.name,\n\t\t\t\t\tinput: args as Record<string, unknown>,\n\t\t\t\t\tcontent: result.content,\n\t\t\t\t\tdetails: result.details,\n\t\t\t\t\tisError,\n\t\t\t\t});\n\t\t\t\treturn patch\n\t\t\t\t\t? { content: patch.content, details: patch.details, isError: patch.isError, terminate: patch.terminate }\n\t\t\t\t\t: undefined;\n\t\t\t},\n\t\t\tprepareNextTurn: async () => {\n\t\t\t\tawait this.flushPendingSessionWrites();\n\t\t\t\tconst nextTurnState = await this.createTurnState();\n\t\t\t\tsetTurnState(nextTurnState);\n\t\t\t\treturn {\n\t\t\t\t\tcontext: this.createContext(nextTurnState),\n\t\t\t\t\tmodel: nextTurnState.model,\n\t\t\t\t\tthinkingLevel: nextTurnState.thinkingLevel,\n\t\t\t\t};\n\t\t\t},\n\t\t\tgetSteeringMessages: async () => this.drainQueuedMessages(this.steerQueue, this.steeringQueueMode),\n\t\t\tgetFollowUpMessages: async () => this.drainQueuedMessages(this.followUpQueue, this.followUpQueueMode),\n\t\t};\n\t}\n\n\tprivate validateUniqueNames(names: string[], message: string): void {\n\t\tconst duplicates = findDuplicateNames(names);\n\t\tif (duplicates.length > 0)\n\t\t\tthrow new AgentHarnessError(\"invalid_argument\", `${message}: ${duplicates.join(\", \")}`);\n\t}\n\n\tprivate validateToolNames(toolNames: string[], tools: Map<string, TTool> = this.tools): void {\n\t\tthis.validateUniqueNames(toolNames, \"Duplicate active tool name(s)\");\n\t\tconst missing = toolNames.filter((name) => !tools.has(name));\n\t\tif (missing.length > 0) throw new AgentHarnessError(\"invalid_argument\", `Unknown tool(s): ${missing.join(\", \")}`);\n\t}\n\n\tprivate async flushPendingSessionWrites(): Promise<void> {\n\t\twhile (this.pendingSessionWrites.length > 0) {\n\t\t\tconst write = this.pendingSessionWrites[0]!;\n\t\t\tif (write.type === \"message\") {\n\t\t\t\tawait this.session.appendMessage(write.message);\n\t\t\t} else if (write.type === \"model_change\") {\n\t\t\t\tawait this.session.appendModelChange(write.provider, write.modelId);\n\t\t\t} else if (write.type === \"thinking_level_change\") {\n\t\t\t\tawait this.session.appendThinkingLevelChange(write.thinkingLevel);\n\t\t\t} else if (write.type === \"active_tools_change\") {\n\t\t\t\tawait this.session.appendActiveToolsChange(write.activeToolNames);\n\t\t\t} else if (write.type === \"custom\") {\n\t\t\t\tawait this.session.appendCustomEntry(write.customType, write.data);\n\t\t\t} else if (write.type === \"custom_message\") {\n\t\t\t\tawait this.session.appendCustomMessageEntry(write.customType, write.content, write.display, write.details);\n\t\t\t} else if (write.type === \"label\") {\n\t\t\t\tawait this.session.appendLabel(write.targetId, write.label);\n\t\t\t} else if (write.type === \"session_info\") {\n\t\t\t\tawait this.session.appendSessionName(write.name ?? \"\");\n\t\t\t} else if (write.type === \"leaf\") {\n\t\t\t\tawait this.session.getStorage().setLeafId(write.targetId);\n\t\t\t}\n\t\t\tthis.pendingSessionWrites.shift();\n\t\t}\n\t}\n\n\tprivate async handleAgentEvent(event: AgentEvent, signal?: AbortSignal): Promise<void> {\n\t\tif (event.type === \"message_end\") {\n\t\t\tawait this.session.appendMessage(event.message);\n\t\t\tawait this.emitAny(event, signal);\n\t\t\treturn;\n\t\t}\n\t\tif (event.type === \"turn_end\") {\n\t\t\tlet eventError: unknown;\n\t\t\ttry {\n\t\t\t\tawait this.emitAny(event, signal);\n\t\t\t} catch (error) {\n\t\t\t\teventError = error;\n\t\t\t}\n\t\t\tconst hadPendingMutations = this.pendingSessionWrites.length > 0;\n\t\t\tawait this.flushPendingSessionWrites();\n\t\t\tif (eventError) throw eventError;\n\t\t\tawait this.emitOwn({ type: \"save_point\", hadPendingMutations });\n\t\t\treturn;\n\t\t}\n\t\tif (event.type === \"agent_end\") {\n\t\t\t// Finish this run's cleanup before flipping to \"idle\": listeners observing\n\t\t\t// agent_end/settled may start the next prompt() immediately, and that run\n\t\t\t// must not inherit state still owned by the run that just completed.\n\t\t\tawait this.flushPendingSessionWrites();\n\t\t\tif (this.runAbortController && this.runAbortController.signal === signal) {\n\t\t\t\tthis.runAbortController = undefined;\n\t\t\t}\n\t\t\tthis.phase = \"idle\";\n\t\t\tawait this.emitAny(event, signal);\n\t\t\tawait this.emitOwn({ type: \"settled\", nextTurnCount: this.nextTurnQueue.length }, signal);\n\t\t\treturn;\n\t\t}\n\t\tawait this.emitAny(event, signal);\n\t}\n\n\tprivate async emitRunFailure(\n\t\tmodel: Model<any>,\n\t\terror: unknown,\n\t\taborted: boolean,\n\t\tsignal: AbortSignal,\n\t\tcompletedMessages: readonly AgentMessage[],\n\t): Promise<AgentMessage[]> {\n\t\tconst failureMessage = createFailureMessage(model, error, aborted);\n\t\tconst messages = [...completedMessages, failureMessage];\n\t\tawait this.handleAgentEvent({ type: \"message_start\", message: failureMessage }, signal);\n\t\tawait this.handleAgentEvent({ type: \"message_end\", message: failureMessage }, signal);\n\t\tawait this.handleAgentEvent({ type: \"turn_end\", message: failureMessage, toolResults: [] }, signal);\n\t\tawait this.handleAgentEvent({ type: \"agent_end\", messages }, signal);\n\t\treturn messages;\n\t}\n\n\tprivate async executeTurn(\n\t\tturnState: AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>,\n\t\ttext: string,\n\t\toptions?: { images?: ImageContent[] },\n\t): Promise<AssistantMessage> {\n\t\tlet activeTurnState = turnState;\n\t\tlet messages: AgentMessage[] = [createUserMessage(text, options?.images)];\n\t\tif (this.nextTurnQueue.length > 0) {\n\t\t\tconst queuedMessages = this.nextTurnQueue.splice(0);\n\t\t\ttry {\n\t\t\t\tawait this.emitQueueUpdate();\n\t\t\t} catch (error) {\n\t\t\t\tthis.nextTurnQueue.unshift(...queuedMessages);\n\t\t\t\tthrow normalizeHookError(error);\n\t\t\t}\n\t\t\tmessages = [...queuedMessages, messages[0]!];\n\t\t}\n\t\tconst beforeResult = await this.emitHook({\n\t\t\ttype: \"before_agent_start\",\n\t\t\tprompt: text,\n\t\t\timages: options?.images,\n\t\t\tsystemPrompt: turnState.systemPrompt,\n\t\t\tresources: turnState.resources,\n\t\t});\n\t\tif (beforeResult?.messages) messages = [...messages, ...beforeResult.messages];\n\n\t\tconst abortController = new AbortController();\n\t\tconst getTurnState = () => activeTurnState;\n\t\tconst setTurnState = (nextTurnState: AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>) => {\n\t\t\tactiveTurnState = nextTurnState;\n\t\t};\n\t\tthis.runAbortController = abortController;\n\t\tconst completedMessages: AgentMessage[] = [];\n\t\tconst runResultPromise = (async () => {\n\t\t\ttry {\n\t\t\t\treturn await runAgentLoop(\n\t\t\t\t\tmessages,\n\t\t\t\t\tthis.createContext(turnState, beforeResult?.systemPrompt),\n\t\t\t\t\tthis.createLoopConfig(getTurnState, setTurnState),\n\t\t\t\t\tasync (event) => {\n\t\t\t\t\t\tif (event.type === \"message_end\") completedMessages.push(event.message);\n\t\t\t\t\t\tawait this.handleAgentEvent(event, abortController.signal);\n\t\t\t\t\t},\n\t\t\t\t\tabortController.signal,\n\t\t\t\t\tthis.createStreamFn(getTurnState),\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\ttry {\n\t\t\t\t\treturn await this.emitRunFailure(\n\t\t\t\t\t\tactiveTurnState.model,\n\t\t\t\t\t\terror,\n\t\t\t\t\t\tabortController.signal.aborted,\n\t\t\t\t\t\tabortController.signal,\n\t\t\t\t\t\tcompletedMessages,\n\t\t\t\t\t);\n\t\t\t\t} catch (failureError) {\n\t\t\t\t\tconst cause = new AggregateError(\n\t\t\t\t\t\t[toError(error), toError(failureError)],\n\t\t\t\t\t\t\"Agent run failed and failure reporting failed\",\n\t\t\t\t\t);\n\t\t\t\t\tthrow new AgentHarnessError(\"unknown\", cause.message, cause);\n\t\t\t\t}\n\t\t\t}\n\t\t})();\n\t\ttry {\n\t\t\tconst newMessages = await runResultPromise;\n\t\t\tfor (let i = newMessages.length - 1; i >= 0; i--) {\n\t\t\t\tconst message = newMessages[i]!;\n\t\t\t\tif (message.role === \"assistant\") {\n\t\t\t\t\treturn message;\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow new AgentHarnessError(\"invalid_state\", \"AgentHarness prompt completed without an assistant message\");\n\t\t} finally {\n\t\t\ttry {\n\t\t\t\tawait this.flushPendingSessionWrites();\n\t\t\t} finally {\n\t\t\t\t// Ownership check: a re-entrant run started from a settled/agent_end\n\t\t\t\t// listener may have installed its own controller already.\n\t\t\t\tif (this.runAbortController === abortController) {\n\t\t\t\t\tthis.runAbortController = undefined;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tasync prompt(text: string, options?: { images?: ImageContent[] }): Promise<AssistantMessage> {\n\t\tif (this.phase !== \"idle\") throw new AgentHarnessError(\"busy\", \"AgentHarness is busy\");\n\t\tthis.phase = \"turn\";\n\t\tconst { runPromise, finishRunPromise } = this.startRunPromise();\n\t\ttry {\n\t\t\tconst turnState = await this.createTurnState();\n\t\t\treturn await this.executeTurn(turnState, text, options);\n\t\t} catch (error) {\n\t\t\t// Only reset the phase if this call still owns the run; a listener may\n\t\t\t// have started the next run while this one was unwinding.\n\t\t\tif (this.runPromise === runPromise) this.phase = \"idle\";\n\t\t\tthrow normalizeHarnessError(error, \"unknown\");\n\t\t} finally {\n\t\t\tfinishRunPromise();\n\t\t}\n\t}\n\n\tasync skill(name: string, additionalInstructions?: string): Promise<AssistantMessage> {\n\t\tif (this.phase !== \"idle\") throw new AgentHarnessError(\"busy\", \"AgentHarness is busy\");\n\t\tthis.phase = \"turn\";\n\t\tconst { runPromise, finishRunPromise } = this.startRunPromise();\n\t\ttry {\n\t\t\tconst turnState = await this.createTurnState();\n\t\t\tconst skill = (turnState.resources.skills ?? []).find((candidate) => candidate.name === name);\n\t\t\tif (!skill) throw new AgentHarnessError(\"invalid_argument\", `Unknown skill: ${name}`);\n\t\t\treturn await this.executeTurn(turnState, formatSkillInvocation(skill, additionalInstructions));\n\t\t} catch (error) {\n\t\t\tif (this.runPromise === runPromise) this.phase = \"idle\";\n\t\t\tthrow normalizeHarnessError(error, \"unknown\");\n\t\t} finally {\n\t\t\tfinishRunPromise();\n\t\t}\n\t}\n\n\tasync promptFromTemplate(name: string, args: string[] = []): Promise<AssistantMessage> {\n\t\tif (this.phase !== \"idle\") throw new AgentHarnessError(\"busy\", \"AgentHarness is busy\");\n\t\tthis.phase = \"turn\";\n\t\tconst { runPromise, finishRunPromise } = this.startRunPromise();\n\t\ttry {\n\t\t\tconst turnState = await this.createTurnState();\n\t\t\tconst template = (turnState.resources.promptTemplates ?? []).find((candidate) => candidate.name === name);\n\t\t\tif (!template) throw new AgentHarnessError(\"invalid_argument\", `Unknown prompt template: ${name}`);\n\t\t\treturn await this.executeTurn(turnState, formatPromptTemplateInvocation(template, args));\n\t\t} catch (error) {\n\t\t\tif (this.runPromise === runPromise) this.phase = \"idle\";\n\t\t\tthrow normalizeHarnessError(error, \"unknown\");\n\t\t} finally {\n\t\t\tfinishRunPromise();\n\t\t}\n\t}\n\n\tasync steer(text: string, options?: { images?: ImageContent[] }): Promise<void> {\n\t\tif (this.phase === \"idle\") throw new AgentHarnessError(\"invalid_state\", \"Cannot steer while idle\");\n\t\tthis.steerQueue.push(createUserMessage(text, options?.images));\n\t\tawait this.emitQueueUpdate();\n\t}\n\n\tasync followUp(text: string, options?: { images?: ImageContent[] }): Promise<void> {\n\t\tif (this.phase === \"idle\") throw new AgentHarnessError(\"invalid_state\", \"Cannot follow up while idle\");\n\t\tthis.followUpQueue.push(createUserMessage(text, options?.images));\n\t\tawait this.emitQueueUpdate();\n\t}\n\n\tasync nextTurn(text: string, options?: { images?: ImageContent[] }): Promise<void> {\n\t\tthis.nextTurnQueue.push(createUserMessage(text, options?.images));\n\t\tawait this.emitQueueUpdate();\n\t}\n\n\tasync appendMessage(message: AgentMessage): Promise<void> {\n\t\ttry {\n\t\t\tif (this.phase === \"idle\") {\n\t\t\t\tawait this.session.appendMessage(message);\n\t\t\t} else {\n\t\t\t\tthis.pendingSessionWrites.push({ type: \"message\", message });\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthrow normalizeHarnessError(error, \"session\");\n\t\t}\n\t}\n\n\tasync compact(\n\t\tcustomInstructions?: string,\n\t): Promise<{ summary: string; firstKeptEntryId: string; tokensBefore: number; details?: unknown }> {\n\t\tif (this.phase !== \"idle\") throw new AgentHarnessError(\"busy\", \"compact() requires idle harness\");\n\t\tthis.phase = \"compaction\";\n\t\ttry {\n\t\t\tconst model = this.model;\n\t\t\tif (!model) throw new AgentHarnessError(\"invalid_state\", \"No model set for compaction\");\n\t\t\tconst auth = await this.getApiKeyAndHeaders?.(model);\n\t\t\tif (!auth) throw new AgentHarnessError(\"auth\", \"No auth available for compaction\");\n\t\t\tconst branchEntries = await this.session.getBranch();\n\t\t\tconst preparationResult = prepareCompaction(branchEntries, DEFAULT_COMPACTION_SETTINGS);\n\t\t\tif (!preparationResult.ok) throw preparationResult.error;\n\t\t\tconst preparation = preparationResult.value;\n\t\t\tif (!preparation) throw new AgentHarnessError(\"compaction\", \"Nothing to compact\");\n\t\t\tconst hookResult = await this.emitHook({\n\t\t\t\ttype: \"session_before_compact\",\n\t\t\t\tpreparation,\n\t\t\t\tbranchEntries,\n\t\t\t\tcustomInstructions,\n\t\t\t\tsignal: new AbortController().signal,\n\t\t\t});\n\t\t\tif (hookResult?.cancel) throw new AgentHarnessError(\"compaction\", \"Compaction cancelled\");\n\t\t\tconst provided = hookResult?.compaction;\n\t\t\tconst compactResult = provided\n\t\t\t\t? { ok: true as const, value: provided }\n\t\t\t\t: await compact(\n\t\t\t\t\t\tpreparation,\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\tauth.apiKey,\n\t\t\t\t\t\tauth.headers,\n\t\t\t\t\t\tcustomInstructions,\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tthis.thinkingLevel,\n\t\t\t\t\t);\n\t\t\tif (!compactResult.ok) throw compactResult.error;\n\t\t\tconst result = compactResult.value;\n\t\t\tconst entryId = await this.session.appendCompaction(\n\t\t\t\tresult.summary,\n\t\t\t\tresult.firstKeptEntryId,\n\t\t\t\tresult.tokensBefore,\n\t\t\t\tresult.details,\n\t\t\t\tprovided !== undefined,\n\t\t\t);\n\t\t\tconst entry = await this.session.getEntry(entryId);\n\t\t\tif (entry?.type === \"compaction\") {\n\t\t\t\tawait this.emitOwn({ type: \"session_compact\", compactionEntry: entry, fromHook: provided !== undefined });\n\t\t\t}\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\tthrow normalizeHarnessError(error, \"compaction\");\n\t\t} finally {\n\t\t\tthis.phase = \"idle\";\n\t\t}\n\t}\n\n\tasync navigateTree(\n\t\ttargetId: string,\n\t\toptions?: { summarize?: boolean; customInstructions?: string; replaceInstructions?: boolean; label?: string },\n\t): Promise<NavigateTreeResult> {\n\t\tif (this.phase !== \"idle\") throw new AgentHarnessError(\"busy\", \"navigateTree() requires idle harness\");\n\t\tthis.phase = \"branch_summary\";\n\t\ttry {\n\t\t\tconst oldLeafId = await this.session.getLeafId();\n\t\t\tif (oldLeafId === targetId) return { cancelled: false };\n\t\t\tconst targetEntry = await this.session.getEntry(targetId);\n\t\t\tif (!targetEntry) throw new AgentHarnessError(\"invalid_argument\", `Entry ${targetId} not found`);\n\t\t\tconst { entries, commonAncestorId } = await collectEntriesForBranchSummary(this.session, oldLeafId, targetId);\n\t\t\tconst preparation = {\n\t\t\t\ttargetId,\n\t\t\t\toldLeafId,\n\t\t\t\tcommonAncestorId,\n\t\t\t\tentriesToSummarize: entries,\n\t\t\t\tuserWantsSummary: options?.summarize ?? false,\n\t\t\t\tcustomInstructions: options?.customInstructions,\n\t\t\t\treplaceInstructions: options?.replaceInstructions,\n\t\t\t\tlabel: options?.label,\n\t\t\t};\n\t\t\tconst signal = new AbortController().signal;\n\t\t\tconst hookResult = await this.emitHook({ type: \"session_before_tree\", preparation, signal });\n\t\t\tif (hookResult?.cancel) return { cancelled: true };\n\t\t\tlet summaryEntry: NavigateTreeResult[\"summaryEntry\"];\n\t\t\tlet summaryText: string | undefined = hookResult?.summary?.summary;\n\t\t\tlet summaryDetails: unknown = hookResult?.summary?.details;\n\t\t\tif (!summaryText && options?.summarize && entries.length > 0) {\n\t\t\t\tconst model = this.model;\n\t\t\t\tif (!model) throw new AgentHarnessError(\"invalid_state\", \"No model set for branch summary\");\n\t\t\t\tconst auth = await this.getApiKeyAndHeaders?.(model);\n\t\t\t\tif (!auth) throw new AgentHarnessError(\"auth\", \"No auth available for branch summary\");\n\t\t\t\tconst branchSummary = await generateBranchSummary(entries, {\n\t\t\t\t\tmodel,\n\t\t\t\t\tapiKey: auth.apiKey,\n\t\t\t\t\theaders: auth.headers,\n\t\t\t\t\tsignal: new AbortController().signal,\n\t\t\t\t\tcustomInstructions: hookResult?.customInstructions ?? options?.customInstructions,\n\t\t\t\t\treplaceInstructions: hookResult?.replaceInstructions ?? options?.replaceInstructions,\n\t\t\t\t});\n\t\t\t\tif (!branchSummary.ok) {\n\t\t\t\t\tif (branchSummary.error.code === \"aborted\") return { cancelled: true };\n\t\t\t\t\tthrow new AgentHarnessError(\"branch_summary\", branchSummary.error.message, branchSummary.error);\n\t\t\t\t}\n\t\t\t\tsummaryText = branchSummary.value.summary;\n\t\t\t\tsummaryDetails = {\n\t\t\t\t\treadFiles: branchSummary.value.readFiles,\n\t\t\t\t\tmodifiedFiles: branchSummary.value.modifiedFiles,\n\t\t\t\t};\n\t\t\t}\n\t\t\tlet editorText: string | undefined;\n\t\t\tlet newLeafId: string | null;\n\t\t\tif (targetEntry.type === \"message\" && targetEntry.message.role === \"user\") {\n\t\t\t\tnewLeafId = targetEntry.parentId;\n\t\t\t\tconst content = targetEntry.message.content;\n\t\t\t\teditorText =\n\t\t\t\t\ttypeof content === \"string\"\n\t\t\t\t\t\t? content\n\t\t\t\t\t\t: content\n\t\t\t\t\t\t\t\t.filter((c): c is { readonly type: \"text\"; readonly text: string } => c.type === \"text\")\n\t\t\t\t\t\t\t\t.map((c) => c.text)\n\t\t\t\t\t\t\t\t.join(\"\");\n\t\t\t} else if (targetEntry.type === \"custom_message\") {\n\t\t\t\tnewLeafId = targetEntry.parentId;\n\t\t\t\teditorText =\n\t\t\t\t\ttypeof targetEntry.content === \"string\"\n\t\t\t\t\t\t? targetEntry.content\n\t\t\t\t\t\t: targetEntry.content\n\t\t\t\t\t\t\t\t.filter((c): c is { readonly type: \"text\"; readonly text: string } => c.type === \"text\")\n\t\t\t\t\t\t\t\t.map((c) => c.text)\n\t\t\t\t\t\t\t\t.join(\"\");\n\t\t\t} else {\n\t\t\t\tnewLeafId = targetId;\n\t\t\t}\n\t\t\tconst summaryId = await this.session.moveTo(\n\t\t\t\tnewLeafId,\n\t\t\t\tsummaryText\n\t\t\t\t\t? { summary: summaryText, details: summaryDetails, fromHook: hookResult?.summary !== undefined }\n\t\t\t\t\t: undefined,\n\t\t\t);\n\t\t\tif (summaryId) {\n\t\t\t\tconst entry = await this.session.getEntry(summaryId);\n\t\t\t\tif (entry?.type === \"branch_summary\") summaryEntry = entry;\n\t\t\t}\n\t\t\tawait this.emitOwn({\n\t\t\t\ttype: \"session_tree\",\n\t\t\t\tnewLeafId: await this.session.getLeafId(),\n\t\t\t\toldLeafId,\n\t\t\t\tsummaryEntry,\n\t\t\t\tfromHook: hookResult?.summary !== undefined,\n\t\t\t});\n\t\t\treturn { cancelled: false, editorText, summaryEntry };\n\t\t} catch (error) {\n\t\t\tthrow normalizeHarnessError(error, \"branch_summary\");\n\t\t} finally {\n\t\t\tthis.phase = \"idle\";\n\t\t}\n\t}\n\n\tgetModel(): Model<any> {\n\t\treturn this.model;\n\t}\n\n\tasync setModel(model: Model<any>): Promise<void> {\n\t\ttry {\n\t\t\tconst previousModel = this.model;\n\t\t\tif (this.phase === \"idle\") {\n\t\t\t\tawait this.session.appendModelChange(model.provider, model.id);\n\t\t\t} else {\n\t\t\t\tthis.pendingSessionWrites.push({ type: \"model_change\", provider: model.provider, modelId: model.id });\n\t\t\t}\n\t\t\tthis.model = model;\n\t\t\tawait this.emitOwn({ type: \"model_update\", model, previousModel, source: \"set\" });\n\t\t} catch (error) {\n\t\t\tthrow normalizeHarnessError(error, \"session\");\n\t\t}\n\t}\n\n\tgetThinkingLevel(): ThinkingLevel {\n\t\treturn this.thinkingLevel;\n\t}\n\n\tasync setThinkingLevel(level: ThinkingLevel): Promise<void> {\n\t\ttry {\n\t\t\tconst previousLevel = this.thinkingLevel;\n\t\t\tif (this.phase === \"idle\") {\n\t\t\t\tawait this.session.appendThinkingLevelChange(level);\n\t\t\t} else {\n\t\t\t\tthis.pendingSessionWrites.push({ type: \"thinking_level_change\", thinkingLevel: level });\n\t\t\t}\n\t\t\tthis.thinkingLevel = level;\n\t\t\tawait this.emitOwn({ type: \"thinking_level_update\", level, previousLevel });\n\t\t} catch (error) {\n\t\t\tthrow normalizeHarnessError(error, \"session\");\n\t\t}\n\t}\n\n\tgetTools(): TTool[] {\n\t\treturn [...this.tools.values()];\n\t}\n\n\tasync setTools(tools: TTool[], activeToolNames?: string[]): Promise<void> {\n\t\ttry {\n\t\t\tthis.validateUniqueNames(\n\t\t\t\ttools.map((tool) => tool.name),\n\t\t\t\t\"Duplicate tool name(s)\",\n\t\t\t);\n\t\t\tconst nextTools = new Map(tools.map((tool) => [tool.name, tool]));\n\t\t\tconst nextActiveToolNames = activeToolNames ? [...activeToolNames] : this.activeToolNames;\n\t\t\tthis.validateToolNames(nextActiveToolNames, nextTools);\n\t\t\tconst previousToolNames = [...this.tools.keys()];\n\t\t\tconst previousActiveToolNames = [...this.activeToolNames];\n\t\t\tif (this.phase === \"idle\") {\n\t\t\t\tawait this.session.appendActiveToolsChange(nextActiveToolNames);\n\t\t\t} else {\n\t\t\t\tthis.pendingSessionWrites.push({ type: \"active_tools_change\", activeToolNames: [...nextActiveToolNames] });\n\t\t\t}\n\t\t\tthis.tools = nextTools;\n\t\t\tthis.activeToolNames = [...nextActiveToolNames];\n\t\t\tawait this.emitOwn({\n\t\t\t\ttype: \"tools_update\",\n\t\t\t\ttoolNames: [...this.tools.keys()],\n\t\t\t\tpreviousToolNames,\n\t\t\t\tactiveToolNames: [...this.activeToolNames],\n\t\t\t\tpreviousActiveToolNames,\n\t\t\t\tsource: \"set\",\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthrow normalizeHarnessError(error, \"invalid_argument\");\n\t\t}\n\t}\n\n\tgetActiveTools(): TTool[] {\n\t\treturn this.activeToolNames.map((name) => this.tools.get(name)!);\n\t}\n\n\tasync setActiveTools(toolNames: string[]): Promise<void> {\n\t\ttry {\n\t\t\tthis.validateToolNames(toolNames);\n\t\t\tconst previousToolNames = [...this.tools.keys()];\n\t\t\tconst previousActiveToolNames = [...this.activeToolNames];\n\t\t\tif (this.phase === \"idle\") {\n\t\t\t\tawait this.session.appendActiveToolsChange(toolNames);\n\t\t\t} else {\n\t\t\t\tthis.pendingSessionWrites.push({ type: \"active_tools_change\", activeToolNames: [...toolNames] });\n\t\t\t}\n\t\t\tthis.activeToolNames = [...toolNames];\n\t\t\tawait this.emitOwn({\n\t\t\t\ttype: \"tools_update\",\n\t\t\t\ttoolNames: [...this.tools.keys()],\n\t\t\t\tpreviousToolNames,\n\t\t\t\tactiveToolNames: [...this.activeToolNames],\n\t\t\t\tpreviousActiveToolNames,\n\t\t\t\tsource: \"set\",\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthrow normalizeHarnessError(error, \"invalid_argument\");\n\t\t}\n\t}\n\n\tgetSteeringMode(): QueueMode {\n\t\treturn this.steeringQueueMode;\n\t}\n\n\tasync setSteeringMode(mode: QueueMode): Promise<void> {\n\t\tthis.steeringQueueMode = mode;\n\t}\n\n\tgetFollowUpMode(): QueueMode {\n\t\treturn this.followUpQueueMode;\n\t}\n\n\tasync setFollowUpMode(mode: QueueMode): Promise<void> {\n\t\tthis.followUpQueueMode = mode;\n\t}\n\n\tgetResources(): AgentHarnessResources<TSkill, TPromptTemplate> {\n\t\treturn {\n\t\t\tskills: this.resources.skills?.slice(),\n\t\t\tpromptTemplates: this.resources.promptTemplates?.slice(),\n\t\t};\n\t}\n\n\tasync setResources(resources: AgentHarnessResources<TSkill, TPromptTemplate>): Promise<void> {\n\t\tconst previousResources = this.getResources();\n\t\tthis.resources = {\n\t\t\tskills: resources.skills?.slice(),\n\t\t\tpromptTemplates: resources.promptTemplates?.slice(),\n\t\t};\n\t\tawait this.emitOwn({ type: \"resources_update\", resources: this.getResources(), previousResources });\n\t}\n\n\tgetStreamOptions(): AgentHarnessStreamOptions {\n\t\treturn cloneStreamOptions(this.streamOptions);\n\t}\n\n\tasync setStreamOptions(streamOptions: AgentHarnessStreamOptions): Promise<void> {\n\t\tthis.streamOptions = cloneStreamOptions(streamOptions);\n\t}\n\n\tasync abort(): Promise<AbortResult> {\n\t\tconst clearedSteer = [...this.steerQueue];\n\t\tconst clearedFollowUp = [...this.followUpQueue];\n\t\tthis.steerQueue = [];\n\t\tthis.followUpQueue = [];\n\t\tthis.runAbortController?.abort();\n\t\tconst errors: Error[] = [];\n\t\ttry {\n\t\t\tawait this.emitQueueUpdate();\n\t\t} catch (error) {\n\t\t\terrors.push(toError(error));\n\t\t}\n\t\ttry {\n\t\t\tawait this.waitForIdle();\n\t\t} catch (error) {\n\t\t\terrors.push(toError(error));\n\t\t}\n\t\ttry {\n\t\t\tawait this.emitOwn({ type: \"abort\", clearedSteer, clearedFollowUp });\n\t\t} catch (error) {\n\t\t\terrors.push(toError(error));\n\t\t}\n\t\tif (errors.length > 0) {\n\t\t\tconst cause = errors.length === 1 ? errors[0]! : new AggregateError(errors, \"Abort completed with errors\");\n\t\t\tthrow normalizeHarnessError(cause, \"hook\");\n\t\t}\n\t\treturn { clearedSteer, clearedFollowUp };\n\t}\n\n\tasync waitForIdle(): Promise<void> {\n\t\t// Runs can chain: a settled listener may start the next prompt() before the\n\t\t// previous run's promise resolves, so keep waiting until no run is active.\n\t\twhile (this.runPromise) {\n\t\t\tawait this.runPromise;\n\t\t}\n\t}\n\n\tsubscribe(\n\t\tlistener: (event: AgentHarnessEvent<TSkill, TPromptTemplate>, signal?: AbortSignal) => Promise<void> | void,\n\t): () => void {\n\t\tlet handlers = this.handlers.get(SUBSCRIBER_EVENT_TYPE);\n\t\tif (!handlers) {\n\t\t\thandlers = new Set();\n\t\t\tthis.handlers.set(SUBSCRIBER_EVENT_TYPE, handlers);\n\t\t}\n\t\thandlers.add(listener as AgentHarnessHandler);\n\t\treturn () => handlers!.delete(listener as AgentHarnessHandler);\n\t}\n\n\ton<TType extends keyof AgentHarnessEventResultMap>(\n\t\ttype: TType,\n\t\thandler: (\n\t\t\tevent: Extract<AgentHarnessOwnEvent, { type: TType }>,\n\t\t) => Promise<AgentHarnessEventResultMap[TType]> | AgentHarnessEventResultMap[TType],\n\t): () => void {\n\t\tlet handlers = this.handlers.get(type);\n\t\tif (!handlers) {\n\t\t\thandlers = new Set();\n\t\t\tthis.handlers.set(type, handlers);\n\t\t}\n\t\thandlers.add(handler as AgentHarnessHandler);\n\t\treturn () => handlers!.delete(handler as AgentHarnessHandler);\n\t}\n}\n"]}
1
+ {"version":3,"file":"agent-harness.d.ts","sourceRoot":"","sources":["../../src/harness/agent-harness.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype AssistantMessage,\n\ttype Context,\n\ttype ImageContent,\n\tisContextOverflow,\n\ttype Model,\n\tstreamSimple,\n\ttype UserMessage,\n} from \"omk-ai\";\nimport { runAgentLoop, runAgentLoopContinue } from \"../agent-loop.ts\";\nimport type {\n\tAgentContext,\n\tAgentEvent,\n\tAgentLoopConfig,\n\tAgentMessage,\n\tAgentTool,\n\tQueueMode,\n\tStreamFn,\n\tThinkingLevel,\n} from \"../types.ts\";\nimport { collectEntriesForBranchSummary, generateBranchSummary } from \"./compaction/branch-summarization.ts\";\nimport {\n\tcompact,\n\tDEFAULT_COMPACTION_SETTINGS,\n\testimateContextTokens,\n\tprepareCompaction,\n\tshouldCompact,\n} from \"./compaction/compaction.ts\";\nimport type { HarnessCompactionRunOptions } from \"./compaction/operation.ts\";\nimport { HarnessSessionFacade } from \"./harness-session.ts\";\nimport { convertToLlm, createFailureMessage, createUserMessage } from \"./messages.ts\";\nimport { findDuplicateNames } from \"./name-validation.ts\";\nimport { formatPromptTemplateInvocation } from \"./prompt-templates.ts\";\nimport { formatSkillInvocation } from \"./skills.ts\";\nimport { applyStreamOptionsPatch, cloneStreamOptions, mergeHeaders } from \"./stream-options.ts\";\nimport { createSummarizationRetry } from \"./summarization-retry.ts\";\nimport type {\n\tAbortResult,\n\tAgentHarnessEvent,\n\tAgentHarnessEventResultMap,\n\tAgentHarnessOptions,\n\tAgentHarnessOwnEvent,\n\tAgentHarnessPhase,\n\tAgentHarnessResources,\n\tAgentHarnessStreamOptions,\n\tCompactionSettings,\n\tCompactResult,\n\tExecutionEnv,\n\tHarnessSession,\n\tNavigateTreeResult,\n\tPendingSessionWrite,\n\tPromptTemplate,\n\tSession,\n\tSkill,\n} from \"./types.ts\";\nimport { AgentHarnessError, BranchSummaryError, CompactionError, SessionError, toError } from \"./types.ts\";\n\nconst SUBSCRIBER_EVENT_TYPE = \"*\";\n\ntype AgentHarnessHandler = (event: any, signal?: AbortSignal) => Promise<any> | any;\n\nfunction normalizeHarnessError(error: unknown, fallbackCode: AgentHarnessError[\"code\"]): AgentHarnessError {\n\tif (error instanceof AgentHarnessError) return error;\n\tconst cause = toError(error);\n\tif (cause instanceof SessionError) return new AgentHarnessError(\"session\", cause.message, cause);\n\tif (cause instanceof CompactionError) return new AgentHarnessError(\"compaction\", cause.message, cause);\n\tif (cause instanceof BranchSummaryError) return new AgentHarnessError(\"branch_summary\", cause.message, cause);\n\treturn new AgentHarnessError(fallbackCode, cause.message, cause);\n}\n\ninterface AgentHarnessTurnState<\n\tTSkill extends Skill = Skill,\n\tTPromptTemplate extends PromptTemplate = PromptTemplate,\n\tTTool extends AgentTool = AgentTool,\n> {\n\tmessages: AgentMessage[];\n\tresources: AgentHarnessResources<TSkill, TPromptTemplate>;\n\tstreamOptions: AgentHarnessStreamOptions;\n\tsessionId: string;\n\tsystemPrompt: string;\n\tmodel: Model<any>;\n\tthinkingLevel: ThinkingLevel;\n\ttools: TTool[];\n\tactiveTools: TTool[];\n}\n\nexport class AgentHarness<\n\tTSkill extends Skill = Skill,\n\tTPromptTemplate extends PromptTemplate = PromptTemplate,\n\tTTool extends AgentTool = AgentTool,\n> {\n\treadonly env: ExecutionEnv;\n\tprivate session: Session;\n\tprivate readonly sessionFacade: HarnessSession;\n\tprivate phase: AgentHarnessPhase = \"idle\";\n\tprivate runAbortController?: AbortController;\n\tprivate runPromise?: Promise<void>;\n\tprivate pendingSessionWrites: PendingSessionWrite[] = [];\n\tprivate model: Model<any>;\n\tprivate thinkingLevel: ThinkingLevel;\n\tprivate systemPrompt: AgentHarnessOptions<TSkill, TPromptTemplate, TTool>[\"systemPrompt\"];\n\tprivate streamOptions: AgentHarnessStreamOptions;\n\tprivate compactionSettings: CompactionSettings;\n\tprivate getApiKeyAndHeaders?: AgentHarnessOptions[\"getApiKeyAndHeaders\"];\n\tprivate resources: AgentHarnessResources<TSkill, TPromptTemplate>;\n\tprivate tools = new Map<string, TTool>();\n\tprivate activeToolNames: string[];\n\tprivate steerQueue: UserMessage[] = [];\n\tprivate steeringQueueMode: QueueMode;\n\tprivate followUpQueue: UserMessage[] = [];\n\tprivate followUpQueueMode: QueueMode;\n\tprivate nextTurnQueue: AgentMessage[] = [];\n\tprivate handlers = new Map<string, Set<AgentHarnessHandler>>();\n\n\tconstructor(options: AgentHarnessOptions<TSkill, TPromptTemplate, TTool>) {\n\t\tthis.env = options.env;\n\t\tthis.session = options.session;\n\t\tthis.sessionFacade = new HarnessSessionFacade(this.session, () => this.phase, this.pendingSessionWrites);\n\t\tthis.resources = options.resources ?? {};\n\t\tthis.streamOptions = cloneStreamOptions(options.streamOptions);\n\t\tthis.compactionSettings = { ...DEFAULT_COMPACTION_SETTINGS, ...options.compaction };\n\t\tthis.systemPrompt = options.systemPrompt;\n\t\tthis.getApiKeyAndHeaders = options.getApiKeyAndHeaders;\n\t\tthis.validateUniqueNames(\n\t\t\t(options.tools ?? []).map((tool) => tool.name),\n\t\t\t\"Duplicate tool name(s)\",\n\t\t);\n\t\tfor (const tool of options.tools ?? []) {\n\t\t\tthis.tools.set(tool.name, tool);\n\t\t}\n\t\tthis.model = options.model;\n\t\tthis.thinkingLevel = options.thinkingLevel ?? \"off\";\n\t\tthis.activeToolNames = options.activeToolNames\n\t\t\t? [...options.activeToolNames]\n\t\t\t: (options.tools ?? []).map((tool) => tool.name);\n\t\tthis.validateUniqueNames(this.activeToolNames, \"Duplicate active tool name(s)\");\n\t\tthis.validateToolNames(this.activeToolNames);\n\t\tthis.steeringQueueMode = options.steeringMode ?? \"one-at-a-time\";\n\t\tthis.followUpQueueMode = options.followUpMode ?? \"one-at-a-time\";\n\t}\n\n\tprivate async emitOwn(event: AgentHarnessOwnEvent<TSkill, TPromptTemplate>, signal?: AbortSignal): Promise<void> {\n\t\tfor (const listener of this.handlers.get(SUBSCRIBER_EVENT_TYPE) ?? []) {\n\t\t\ttry {\n\t\t\t\tawait listener(event, signal);\n\t\t\t} catch (error) {\n\t\t\t\tthrow normalizeHarnessError(error, \"hook\");\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async emitAny(event: AgentHarnessEvent<TSkill, TPromptTemplate>, signal?: AbortSignal): Promise<void> {\n\t\tfor (const listener of this.handlers.get(SUBSCRIBER_EVENT_TYPE) ?? []) {\n\t\t\ttry {\n\t\t\t\tawait listener(event, signal);\n\t\t\t} catch (error) {\n\t\t\t\tthrow normalizeHarnessError(error, \"hook\");\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async emitHook<TType extends keyof AgentHarnessEventResultMap>(\n\t\tevent: Extract<AgentHarnessOwnEvent, { type: TType }>,\n\t): Promise<AgentHarnessEventResultMap[TType] | undefined> {\n\t\tconst handlers = this.handlers.get(event.type as TType);\n\t\tif (!handlers || handlers.size === 0) return undefined;\n\t\tlet lastResult: AgentHarnessEventResultMap[TType] | undefined;\n\t\tfor (const handler of handlers) {\n\t\t\ttry {\n\t\t\t\tconst result = await handler(event);\n\t\t\t\tif (result !== undefined) {\n\t\t\t\t\tlastResult = result;\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tthrow normalizeHarnessError(error, \"hook\");\n\t\t\t}\n\t\t}\n\t\treturn lastResult;\n\t}\n\n\tprivate async emitBeforeProviderRequest(\n\t\tmodel: Model<any>,\n\t\tsessionId: string,\n\t\tstreamOptions: AgentHarnessStreamOptions,\n\t): Promise<AgentHarnessStreamOptions> {\n\t\tconst handlers = this.handlers.get(\"before_provider_request\");\n\t\tlet current = cloneStreamOptions(streamOptions);\n\t\tif (!handlers || handlers.size === 0) return current;\n\t\tfor (const handler of handlers) {\n\t\t\ttry {\n\t\t\t\tconst result = await handler({\n\t\t\t\t\ttype: \"before_provider_request\",\n\t\t\t\t\tmodel,\n\t\t\t\t\tsessionId,\n\t\t\t\t\tstreamOptions: cloneStreamOptions(current),\n\t\t\t\t});\n\t\t\t\tif (result?.streamOptions) {\n\t\t\t\t\tcurrent = applyStreamOptionsPatch(current, result.streamOptions);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tthrow normalizeHarnessError(error, \"hook\");\n\t\t\t}\n\t\t}\n\t\treturn current;\n\t}\n\n\tprivate async emitBeforeProviderPayload(model: Model<any>, payload: unknown): Promise<unknown> {\n\t\tconst handlers = this.handlers.get(\"before_provider_payload\");\n\t\tlet current = payload;\n\t\tif (!handlers || handlers.size === 0) return current;\n\t\tfor (const handler of handlers) {\n\t\t\ttry {\n\t\t\t\tconst result = await handler({ type: \"before_provider_payload\", model, payload: current });\n\t\t\t\tif (result !== undefined) {\n\t\t\t\t\tcurrent = result.payload;\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tthrow normalizeHarnessError(error, \"hook\");\n\t\t\t}\n\t\t}\n\t\treturn current;\n\t}\n\n\tprivate async emitQueueUpdate(): Promise<void> {\n\t\tawait this.emitOwn({\n\t\t\ttype: \"queue_update\",\n\t\t\tsteer: [...this.steerQueue],\n\t\t\tfollowUp: [...this.followUpQueue],\n\t\t\tnextTurn: [...this.nextTurnQueue],\n\t\t});\n\t}\n\n\tprivate startRunPromise(): { runPromise: Promise<void>; finishRunPromise: () => void } {\n\t\tlet finish = () => {};\n\t\tconst runPromise = new Promise<void>((resolve) => {\n\t\t\tfinish = resolve;\n\t\t});\n\t\tthis.runPromise = runPromise;\n\t\treturn {\n\t\t\trunPromise,\n\t\t\tfinishRunPromise: () => {\n\t\t\t\t// A settled/agent_end listener may have started a new run while this\n\t\t\t\t// one was still unwinding; only clear state still owned by this run.\n\t\t\t\tif (this.runPromise === runPromise) {\n\t\t\t\t\tthis.runPromise = undefined;\n\t\t\t\t}\n\t\t\t\tfinish();\n\t\t\t},\n\t\t};\n\t}\n\n\tprivate async createTurnState(): Promise<AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>> {\n\t\tconst context = await this.session.buildContext();\n\t\tconst resources = this.getResources();\n\t\tconst sessionMetadata = await this.session.getMetadata();\n\t\tconst tools = [...this.tools.values()];\n\t\tconst activeTools = this.activeToolNames\n\t\t\t.map((name) => this.tools.get(name))\n\t\t\t.filter((tool): tool is TTool => tool !== undefined);\n\t\tlet systemPrompt = \"You are a helpful assistant.\";\n\t\tif (typeof this.systemPrompt === \"string\") {\n\t\t\tsystemPrompt = this.systemPrompt;\n\t\t} else if (this.systemPrompt) {\n\t\t\tsystemPrompt = await this.systemPrompt({\n\t\t\t\tenv: this.env,\n\t\t\t\tsession: this.session,\n\t\t\t\tmodel: this.model,\n\t\t\t\tthinkingLevel: this.thinkingLevel,\n\t\t\t\tactiveTools,\n\t\t\t\tresources,\n\t\t\t});\n\t\t}\n\t\treturn {\n\t\t\tmessages: context.messages,\n\t\t\tresources,\n\t\t\tstreamOptions: cloneStreamOptions(this.streamOptions),\n\t\t\tsessionId: sessionMetadata.id,\n\t\t\tsystemPrompt,\n\t\t\tmodel: this.model,\n\t\t\tthinkingLevel: this.thinkingLevel,\n\t\t\ttools,\n\t\t\tactiveTools,\n\t\t};\n\t}\n\n\tprivate createContext(\n\t\tturnState: AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>,\n\t\tsystemPrompt?: string,\n\t): AgentContext {\n\t\treturn {\n\t\t\tsystemPrompt: systemPrompt ?? turnState.systemPrompt,\n\t\t\tmessages: turnState.messages.slice(),\n\t\t\ttools: turnState.activeTools.slice(),\n\t\t};\n\t}\n\n\tprivate createStreamFn(getTurnState: () => AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>): StreamFn {\n\t\treturn async (model, context, streamOptions) => {\n\t\t\tconst turnState = getTurnState();\n\t\t\tconst requestContext = await this.maybeAutoCompact(model, context, streamOptions?.signal);\n\t\t\tconst auth = await this.getApiKeyAndHeaders?.(model);\n\t\t\tconst snapshotOptions: AgentHarnessStreamOptions = {\n\t\t\t\t...turnState.streamOptions,\n\t\t\t\theaders: mergeHeaders(turnState.streamOptions.headers, auth?.headers),\n\t\t\t};\n\t\t\tconst requestOptions = await this.emitBeforeProviderRequest(model, turnState.sessionId, snapshotOptions);\n\t\t\treturn streamSimple(model, requestContext, {\n\t\t\t\tcacheRetention: requestOptions.cacheRetention,\n\t\t\t\theaders: requestOptions.headers,\n\t\t\t\tmaxRetries: requestOptions.maxRetries,\n\t\t\t\tmaxRetryDelayMs: requestOptions.maxRetryDelayMs,\n\t\t\t\tmetadata: requestOptions.metadata,\n\t\t\t\tonPayload: async (payload) => await this.emitBeforeProviderPayload(model, payload),\n\t\t\t\tonResponse: async (response) => {\n\t\t\t\t\tconst headers = { ...(response.headers as Record<string, string>) };\n\t\t\t\t\tawait this.emitOwn(\n\t\t\t\t\t\t{ type: \"after_provider_response\", status: response.status, headers },\n\t\t\t\t\t\tstreamOptions?.signal,\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t\treasoning: streamOptions?.reasoning,\n\t\t\t\tsignal: streamOptions?.signal,\n\t\t\t\tsessionId: turnState.sessionId,\n\t\t\t\ttimeoutMs: requestOptions.timeoutMs,\n\t\t\t\ttransport: requestOptions.transport,\n\t\t\t\tapiKey: auth?.apiKey,\n\t\t\t});\n\t\t};\n\t}\n\n\tprivate async drainQueuedMessages(queue: AgentMessage[], mode: QueueMode): Promise<AgentMessage[]> {\n\t\tconst messages = mode === \"all\" ? queue.splice(0) : queue.splice(0, 1);\n\t\tif (messages.length === 0) return messages;\n\t\ttry {\n\t\t\tawait this.emitQueueUpdate();\n\t\t\treturn messages;\n\t\t} catch (error) {\n\t\t\tqueue.unshift(...messages);\n\t\t\tthrow normalizeHarnessError(error, \"hook\");\n\t\t}\n\t}\n\n\tprivate createLoopConfig(\n\t\tgetTurnState: () => AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>,\n\t\tsetTurnState: (turnState: AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>) => void,\n\t): AgentLoopConfig {\n\t\tconst turnState = getTurnState();\n\t\treturn {\n\t\t\tmodel: turnState.model,\n\t\t\treasoning: turnState.thinkingLevel === \"off\" ? undefined : turnState.thinkingLevel,\n\t\t\tconvertToLlm,\n\t\t\ttransformContext: async (messages) => {\n\t\t\t\tconst result = await this.emitHook({ type: \"context\", messages: [...messages] });\n\t\t\t\treturn result?.messages ?? messages;\n\t\t\t},\n\t\t\tbeforeToolCall: async ({ toolCall, args }) => {\n\t\t\t\tconst result = await this.emitHook({\n\t\t\t\t\ttype: \"tool_call\",\n\t\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\t\ttoolName: toolCall.name,\n\t\t\t\t\tinput: args as Record<string, unknown>,\n\t\t\t\t});\n\t\t\t\treturn result ? { block: result.block, reason: result.reason } : undefined;\n\t\t\t},\n\t\t\tafterToolCall: async ({ toolCall, args, result, isError }) => {\n\t\t\t\tconst patch = await this.emitHook({\n\t\t\t\t\ttype: \"tool_result\",\n\t\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\t\ttoolName: toolCall.name,\n\t\t\t\t\tinput: args as Record<string, unknown>,\n\t\t\t\t\tcontent: result.content,\n\t\t\t\t\tdetails: result.details,\n\t\t\t\t\tisError,\n\t\t\t\t});\n\t\t\t\treturn patch\n\t\t\t\t\t? { content: patch.content, details: patch.details, isError: patch.isError, terminate: patch.terminate }\n\t\t\t\t\t: undefined;\n\t\t\t},\n\t\t\tprepareNextTurn: async () => {\n\t\t\t\tawait this.flushPendingSessionWrites();\n\t\t\t\tconst nextTurnState = await this.createTurnState();\n\t\t\t\tsetTurnState(nextTurnState);\n\t\t\t\treturn {\n\t\t\t\t\tcontext: this.createContext(nextTurnState),\n\t\t\t\t\tmodel: nextTurnState.model,\n\t\t\t\t\tthinkingLevel: nextTurnState.thinkingLevel,\n\t\t\t\t};\n\t\t\t},\n\t\t\tgetSteeringMessages: async () => this.drainQueuedMessages(this.steerQueue, this.steeringQueueMode),\n\t\t\tgetFollowUpMessages: async () => this.drainQueuedMessages(this.followUpQueue, this.followUpQueueMode),\n\t\t};\n\t}\n\n\tprivate validateUniqueNames(names: string[], message: string): void {\n\t\tconst duplicates = findDuplicateNames(names);\n\t\tif (duplicates.length > 0)\n\t\t\tthrow new AgentHarnessError(\"invalid_argument\", `${message}: ${duplicates.join(\", \")}`);\n\t}\n\n\tprivate validateToolNames(toolNames: string[], tools: Map<string, TTool> = this.tools): void {\n\t\tthis.validateUniqueNames(toolNames, \"Duplicate active tool name(s)\");\n\t\tconst missing = toolNames.filter((name) => !tools.has(name));\n\t\tif (missing.length > 0) throw new AgentHarnessError(\"invalid_argument\", `Unknown tool(s): ${missing.join(\", \")}`);\n\t}\n\n\tprivate async flushPendingSessionWrites(): Promise<void> {\n\t\twhile (this.pendingSessionWrites.length > 0) {\n\t\t\tconst write = this.pendingSessionWrites[0]!;\n\t\t\tif (write.type === \"message\") {\n\t\t\t\tawait this.session.appendMessage(write.message);\n\t\t\t} else if (write.type === \"model_change\") {\n\t\t\t\tawait this.session.appendModelChange(write.provider, write.modelId);\n\t\t\t} else if (write.type === \"thinking_level_change\") {\n\t\t\t\tawait this.session.appendThinkingLevelChange(write.thinkingLevel);\n\t\t\t} else if (write.type === \"active_tools_change\") {\n\t\t\t\tawait this.session.appendActiveToolsChange(write.activeToolNames);\n\t\t\t} else if (write.type === \"custom\") {\n\t\t\t\tawait this.session.appendCustomEntry(write.customType, write.data);\n\t\t\t} else if (write.type === \"custom_message\") {\n\t\t\t\tawait this.session.appendCustomMessageEntry(write.customType, write.content, write.display, write.details);\n\t\t\t} else if (write.type === \"label\") {\n\t\t\t\tawait this.session.appendLabel(write.targetId, write.label);\n\t\t\t} else if (write.type === \"session_info\") {\n\t\t\t\tawait this.session.appendSessionName(write.name ?? \"\");\n\t\t\t} else if (write.type === \"leaf\") {\n\t\t\t\tawait this.session.getStorage().setLeafId(write.targetId);\n\t\t\t}\n\t\t\tthis.pendingSessionWrites.shift();\n\t\t}\n\t}\n\n\tprivate async handleAgentEvent(event: AgentEvent, signal?: AbortSignal): Promise<void> {\n\t\tif (event.type === \"message_end\") {\n\t\t\tawait this.session.appendMessage(event.message);\n\t\t\tawait this.emitAny(event, signal);\n\t\t\treturn;\n\t\t}\n\t\tif (event.type === \"turn_end\") {\n\t\t\tlet eventError: unknown;\n\t\t\ttry {\n\t\t\t\tawait this.emitAny(event, signal);\n\t\t\t} catch (error) {\n\t\t\t\teventError = error;\n\t\t\t}\n\t\t\tconst hadPendingMutations = this.pendingSessionWrites.length > 0;\n\t\t\tawait this.flushPendingSessionWrites();\n\t\t\tif (eventError) throw eventError;\n\t\t\tawait this.emitOwn({ type: \"save_point\", hadPendingMutations });\n\t\t\treturn;\n\t\t}\n\t\tif (event.type === \"agent_end\") {\n\t\t\t// Finish this run's cleanup before flipping to \"idle\": listeners observing\n\t\t\t// agent_end/settled may start the next prompt() immediately, and that run\n\t\t\t// must not inherit state still owned by the run that just completed.\n\t\t\tawait this.flushPendingSessionWrites();\n\t\t\tif (this.runAbortController && this.runAbortController.signal === signal) {\n\t\t\t\tthis.runAbortController = undefined;\n\t\t\t}\n\t\t\tthis.phase = \"idle\";\n\t\t\tawait this.emitAny(event, signal);\n\t\t\tawait this.emitOwn({ type: \"settled\", nextTurnCount: this.nextTurnQueue.length }, signal);\n\t\t\treturn;\n\t\t}\n\t\tawait this.emitAny(event, signal);\n\t}\n\n\tprivate async emitRunFailure(\n\t\tmodel: Model<any>,\n\t\terror: unknown,\n\t\taborted: boolean,\n\t\tsignal: AbortSignal,\n\t\tcompletedMessages: readonly AgentMessage[],\n\t): Promise<AgentMessage[]> {\n\t\tconst failureMessage = createFailureMessage(model, error, aborted);\n\t\tconst messages = [...completedMessages, failureMessage];\n\t\tawait this.handleAgentEvent({ type: \"message_start\", message: failureMessage }, signal);\n\t\tawait this.handleAgentEvent({ type: \"message_end\", message: failureMessage }, signal);\n\t\tawait this.handleAgentEvent({ type: \"turn_end\", message: failureMessage, toolResults: [] }, signal);\n\t\tawait this.handleAgentEvent({ type: \"agent_end\", messages }, signal);\n\t\treturn messages;\n\t}\n\n\tprivate async executeTurn(\n\t\tturnState: AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>,\n\t\ttext: string,\n\t\toptions?: { images?: ImageContent[] },\n\t): Promise<AssistantMessage> {\n\t\tconst runOwner = this.runPromise;\n\t\tlet messages: AgentMessage[] = [createUserMessage(text, options?.images)];\n\t\tif (this.nextTurnQueue.length > 0) {\n\t\t\tconst queuedMessages = this.nextTurnQueue.splice(0);\n\t\t\ttry {\n\t\t\t\tawait this.emitQueueUpdate();\n\t\t\t} catch (error) {\n\t\t\t\tthis.nextTurnQueue.unshift(...queuedMessages);\n\t\t\t\tthrow normalizeHarnessError(error, \"hook\");\n\t\t\t}\n\t\t\tmessages = [...queuedMessages, messages[0]!];\n\t\t}\n\t\tconst beforeResult = await this.emitHook({\n\t\t\ttype: \"before_agent_start\",\n\t\t\tprompt: text,\n\t\t\timages: options?.images,\n\t\t\tsystemPrompt: turnState.systemPrompt,\n\t\t\tresources: turnState.resources,\n\t\t});\n\t\tif (beforeResult?.messages) messages = [...messages, ...beforeResult.messages];\n\n\t\tconst result = await this.executeAgentRun(\n\t\t\tturnState,\n\t\t\tthis.createContext(turnState, beforeResult?.systemPrompt),\n\t\t\tmessages,\n\t\t);\n\t\treturn await this.recoverContextOverflow(result, runOwner);\n\t}\n\n\tprivate async executeAgentRun(\n\t\tturnState: AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>,\n\t\tcontext: AgentContext,\n\t\tinitialMessages?: AgentMessage[],\n\t): Promise<AssistantMessage> {\n\t\tlet activeTurnState = turnState;\n\t\tconst abortController = new AbortController();\n\t\tconst getTurnState = () => activeTurnState;\n\t\tconst setTurnState = (nextTurnState: AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>) => {\n\t\t\tactiveTurnState = nextTurnState;\n\t\t};\n\t\tthis.runAbortController = abortController;\n\t\tconst completedMessages: AgentMessage[] = [];\n\t\tconst emit = async (event: AgentEvent): Promise<void> => {\n\t\t\tif (event.type === \"message_end\") completedMessages.push(event.message);\n\t\t\tawait this.handleAgentEvent(event, abortController.signal);\n\t\t};\n\t\tconst runResultPromise = (async () => {\n\t\t\ttry {\n\t\t\t\tconst loopConfig = this.createLoopConfig(getTurnState, setTurnState);\n\t\t\t\tconst streamFn = this.createStreamFn(getTurnState);\n\t\t\t\treturn initialMessages\n\t\t\t\t\t? await runAgentLoop(initialMessages, context, loopConfig, emit, abortController.signal, streamFn)\n\t\t\t\t\t: await runAgentLoopContinue(context, loopConfig, emit, abortController.signal, streamFn);\n\t\t\t} catch (error) {\n\t\t\t\ttry {\n\t\t\t\t\treturn await this.emitRunFailure(\n\t\t\t\t\t\tactiveTurnState.model,\n\t\t\t\t\t\terror,\n\t\t\t\t\t\tabortController.signal.aborted,\n\t\t\t\t\t\tabortController.signal,\n\t\t\t\t\t\tcompletedMessages,\n\t\t\t\t\t);\n\t\t\t\t} catch (failureError) {\n\t\t\t\t\tconst cause = new AggregateError(\n\t\t\t\t\t\t[toError(error), toError(failureError)],\n\t\t\t\t\t\t\"Agent run failed and failure reporting failed\",\n\t\t\t\t\t);\n\t\t\t\t\tthrow new AgentHarnessError(\"unknown\", cause.message, cause);\n\t\t\t\t}\n\t\t\t}\n\t\t})();\n\t\ttry {\n\t\t\tconst newMessages = await runResultPromise;\n\t\t\tfor (let i = newMessages.length - 1; i >= 0; i--) {\n\t\t\t\tconst message = newMessages[i]!;\n\t\t\t\tif (message.role === \"assistant\") return message;\n\t\t\t}\n\t\t\tthrow new AgentHarnessError(\"invalid_state\", \"AgentHarness prompt completed without an assistant message\");\n\t\t} finally {\n\t\t\ttry {\n\t\t\t\tawait this.flushPendingSessionWrites();\n\t\t\t} finally {\n\t\t\t\tif (this.runAbortController === abortController) this.runAbortController = undefined;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async recoverContextOverflow(\n\t\tmessage: AssistantMessage,\n\t\trunOwner: Promise<void> | undefined,\n\t): Promise<AssistantMessage> {\n\t\tif (\n\t\t\tthis.runPromise !== runOwner ||\n\t\t\tthis.phase !== \"idle\" ||\n\t\t\t!this.compactionSettings.enabled ||\n\t\t\t!isContextOverflow(message, this.model.contextWindow)\n\t\t) {\n\t\t\treturn message;\n\t\t}\n\t\tif (!this.getApiKeyAndHeaders) return message;\n\t\tconst leafId = await this.session.getLeafId();\n\t\tif (!leafId) return message;\n\t\tconst leaf = await this.session.getEntry(leafId);\n\t\tif (\n\t\t\tleaf?.type !== \"message\" ||\n\t\t\tleaf.message.role !== \"assistant\" ||\n\t\t\tleaf.message.timestamp !== message.timestamp ||\n\t\t\t!isContextOverflow(leaf.message, this.model.contextWindow)\n\t\t) {\n\t\t\treturn message;\n\t\t}\n\n\t\tawait this.session.moveTo(leaf.parentId);\n\t\tthis.phase = \"retry\";\n\t\ttry {\n\t\t\tconst compacted = await this.runCompaction({ automatic: true });\n\t\t\tif (!compacted) {\n\t\t\t\tawait this.session.moveTo(leafId);\n\t\t\t\tthis.phase = \"idle\";\n\t\t\t\treturn message;\n\t\t\t}\n\t\t\tconst turnState = await this.createTurnState();\n\t\t\treturn await this.executeAgentRun(turnState, this.createContext(turnState));\n\t\t} catch (error) {\n\t\t\tawait this.session.moveTo(leafId);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync prompt(text: string, options?: { images?: ImageContent[] }): Promise<AssistantMessage> {\n\t\tif (this.phase !== \"idle\") throw new AgentHarnessError(\"busy\", \"AgentHarness is busy\");\n\t\tthis.phase = \"turn\";\n\t\tconst { runPromise, finishRunPromise } = this.startRunPromise();\n\t\ttry {\n\t\t\tconst turnState = await this.createTurnState();\n\t\t\treturn await this.executeTurn(turnState, text, options);\n\t\t} catch (error) {\n\t\t\t// Only reset the phase if this call still owns the run; a listener may\n\t\t\t// have started the next run while this one was unwinding.\n\t\t\tif (this.runPromise === runPromise) this.phase = \"idle\";\n\t\t\tthrow normalizeHarnessError(error, \"unknown\");\n\t\t} finally {\n\t\t\tfinishRunPromise();\n\t\t}\n\t}\n\n\tasync skill(name: string, additionalInstructions?: string): Promise<AssistantMessage> {\n\t\tif (this.phase !== \"idle\") throw new AgentHarnessError(\"busy\", \"AgentHarness is busy\");\n\t\tthis.phase = \"turn\";\n\t\tconst { runPromise, finishRunPromise } = this.startRunPromise();\n\t\ttry {\n\t\t\tconst turnState = await this.createTurnState();\n\t\t\tconst skill = (turnState.resources.skills ?? []).find((candidate) => candidate.name === name);\n\t\t\tif (!skill) throw new AgentHarnessError(\"invalid_argument\", `Unknown skill: ${name}`);\n\t\t\treturn await this.executeTurn(turnState, formatSkillInvocation(skill, additionalInstructions));\n\t\t} catch (error) {\n\t\t\tif (this.runPromise === runPromise) this.phase = \"idle\";\n\t\t\tthrow normalizeHarnessError(error, \"unknown\");\n\t\t} finally {\n\t\t\tfinishRunPromise();\n\t\t}\n\t}\n\n\tasync promptFromTemplate(name: string, args: string[] = []): Promise<AssistantMessage> {\n\t\tif (this.phase !== \"idle\") throw new AgentHarnessError(\"busy\", \"AgentHarness is busy\");\n\t\tthis.phase = \"turn\";\n\t\tconst { runPromise, finishRunPromise } = this.startRunPromise();\n\t\ttry {\n\t\t\tconst turnState = await this.createTurnState();\n\t\t\tconst template = (turnState.resources.promptTemplates ?? []).find((candidate) => candidate.name === name);\n\t\t\tif (!template) throw new AgentHarnessError(\"invalid_argument\", `Unknown prompt template: ${name}`);\n\t\t\treturn await this.executeTurn(turnState, formatPromptTemplateInvocation(template, args));\n\t\t} catch (error) {\n\t\t\tif (this.runPromise === runPromise) this.phase = \"idle\";\n\t\t\tthrow normalizeHarnessError(error, \"unknown\");\n\t\t} finally {\n\t\t\tfinishRunPromise();\n\t\t}\n\t}\n\n\tasync steer(text: string, options?: { images?: ImageContent[] }): Promise<void> {\n\t\tif (this.phase === \"idle\") throw new AgentHarnessError(\"invalid_state\", \"Cannot steer while idle\");\n\t\tthis.steerQueue.push(createUserMessage(text, options?.images));\n\t\tawait this.emitQueueUpdate();\n\t}\n\n\tasync followUp(text: string, options?: { images?: ImageContent[] }): Promise<void> {\n\t\tif (this.phase === \"idle\") throw new AgentHarnessError(\"invalid_state\", \"Cannot follow up while idle\");\n\t\tthis.followUpQueue.push(createUserMessage(text, options?.images));\n\t\tawait this.emitQueueUpdate();\n\t}\n\n\tasync nextTurn(text: string, options?: { images?: ImageContent[] }): Promise<void> {\n\t\tthis.nextTurnQueue.push(createUserMessage(text, options?.images));\n\t\tawait this.emitQueueUpdate();\n\t}\n\n\tgetSession(): HarnessSession {\n\t\treturn this.sessionFacade;\n\t}\n\n\tasync appendMessage(message: AgentMessage): Promise<void> {\n\t\tawait this.sessionFacade.appendMessage(message);\n\t}\n\n\tprivate async runCompaction(options: HarnessCompactionRunOptions): Promise<CompactResult | undefined> {\n\t\tconst model = this.model;\n\t\tconst auth = await this.getApiKeyAndHeaders?.(model);\n\t\tif (!auth) {\n\t\t\tif (options.automatic) return undefined;\n\t\t\tthrow new AgentHarnessError(\"auth\", \"No auth available for compaction\");\n\t\t}\n\t\tconst branchEntries = await this.session.getBranch();\n\t\tconst preparationResult = prepareCompaction(branchEntries, this.compactionSettings);\n\t\tif (!preparationResult.ok) throw preparationResult.error;\n\t\tconst preparation = preparationResult.value;\n\t\tif (!preparation) {\n\t\t\tif (options.automatic) return undefined;\n\t\t\tthrow new AgentHarnessError(\"compaction\", \"Nothing to compact\");\n\t\t}\n\t\tconst signal = options.signal ?? new AbortController().signal;\n\t\tconst hookResult = await this.emitHook({\n\t\t\ttype: \"session_before_compact\",\n\t\t\tpreparation,\n\t\t\tbranchEntries,\n\t\t\tcustomInstructions: options.customInstructions,\n\t\t\tsignal,\n\t\t});\n\t\tif (hookResult?.cancel) {\n\t\t\tif (options.automatic) return undefined;\n\t\t\tthrow new AgentHarnessError(\"compaction\", \"Compaction cancelled\");\n\t\t}\n\t\tconst provided = hookResult?.compaction;\n\t\tconst compactResult = provided\n\t\t\t? { ok: true as const, value: provided }\n\t\t\t: await compact(\n\t\t\t\t\tpreparation,\n\t\t\t\t\tmodel,\n\t\t\t\t\tauth.apiKey,\n\t\t\t\t\tauth.headers,\n\t\t\t\t\toptions.customInstructions,\n\t\t\t\t\tsignal,\n\t\t\t\t\tthis.thinkingLevel,\n\t\t\t\t\tcreateSummarizationRetry(\"compaction\", this.streamOptions.summarizationRetry, (event) =>\n\t\t\t\t\t\tthis.emitOwn(event),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\tif (!compactResult.ok) throw compactResult.error;\n\t\tconst result = compactResult.value;\n\t\tconst entryId = await this.session.appendCompaction(\n\t\t\tresult.summary,\n\t\t\tresult.firstKeptEntryId,\n\t\t\tresult.tokensBefore,\n\t\t\tresult.details,\n\t\t\tprovided !== undefined,\n\t\t);\n\t\tconst entry = await this.session.getEntry(entryId);\n\t\tif (entry?.type === \"compaction\") {\n\t\t\tawait this.emitOwn({ type: \"session_compact\", compactionEntry: entry, fromHook: provided !== undefined });\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate async maybeAutoCompact(model: Model<any>, context: Context, signal?: AbortSignal): Promise<Context> {\n\t\tconst projected = estimateContextTokens(context.messages).tokens;\n\t\tif (!shouldCompact(projected, model.contextWindow ?? 0, this.compactionSettings)) return context;\n\t\tconst result = await this.runCompaction({ automatic: true, signal });\n\t\tif (!result) return context;\n\t\tconst persisted = await this.session.buildContext();\n\t\treturn { ...context, messages: convertToLlm(persisted.messages) };\n\t}\n\n\tasync compact(customInstructions?: string): Promise<CompactResult> {\n\t\tif (this.phase !== \"idle\") throw new AgentHarnessError(\"busy\", \"compact() requires idle harness\");\n\t\tthis.phase = \"compaction\";\n\t\ttry {\n\t\t\tconst result = await this.runCompaction({ automatic: false, customInstructions });\n\t\t\tif (!result) throw new AgentHarnessError(\"compaction\", \"Nothing to compact\");\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\tthrow normalizeHarnessError(error, \"compaction\");\n\t\t} finally {\n\t\t\tthis.phase = \"idle\";\n\t\t}\n\t}\n\n\tasync navigateTree(\n\t\ttargetId: string,\n\t\toptions?: { summarize?: boolean; customInstructions?: string; replaceInstructions?: boolean; label?: string },\n\t): Promise<NavigateTreeResult> {\n\t\tif (this.phase !== \"idle\") throw new AgentHarnessError(\"busy\", \"navigateTree() requires idle harness\");\n\t\tthis.phase = \"branch_summary\";\n\t\ttry {\n\t\t\tconst oldLeafId = await this.session.getLeafId();\n\t\t\tif (oldLeafId === targetId) return { cancelled: false };\n\t\t\tconst targetEntry = await this.session.getEntry(targetId);\n\t\t\tif (!targetEntry) throw new AgentHarnessError(\"invalid_argument\", `Entry ${targetId} not found`);\n\t\t\tconst { entries, commonAncestorId } = await collectEntriesForBranchSummary(this.session, oldLeafId, targetId);\n\t\t\tconst preparation = {\n\t\t\t\ttargetId,\n\t\t\t\toldLeafId,\n\t\t\t\tcommonAncestorId,\n\t\t\t\tentriesToSummarize: entries,\n\t\t\t\tuserWantsSummary: options?.summarize ?? false,\n\t\t\t\tcustomInstructions: options?.customInstructions,\n\t\t\t\treplaceInstructions: options?.replaceInstructions,\n\t\t\t\tlabel: options?.label,\n\t\t\t};\n\t\t\tconst signal = new AbortController().signal;\n\t\t\tconst hookResult = await this.emitHook({ type: \"session_before_tree\", preparation, signal });\n\t\t\tif (hookResult?.cancel) return { cancelled: true };\n\t\t\tlet summaryEntry: NavigateTreeResult[\"summaryEntry\"];\n\t\t\tlet summaryText: string | undefined = hookResult?.summary?.summary;\n\t\t\tlet summaryDetails: unknown = hookResult?.summary?.details;\n\t\t\tif (!summaryText && options?.summarize && entries.length > 0) {\n\t\t\t\tconst model = this.model;\n\t\t\t\tif (!model) throw new AgentHarnessError(\"invalid_state\", \"No model set for branch summary\");\n\t\t\t\tconst auth = await this.getApiKeyAndHeaders?.(model);\n\t\t\t\tif (!auth) throw new AgentHarnessError(\"auth\", \"No auth available for branch summary\");\n\t\t\t\tconst branchSummary = await generateBranchSummary(entries, {\n\t\t\t\t\tmodel,\n\t\t\t\t\tapiKey: auth.apiKey,\n\t\t\t\t\theaders: auth.headers,\n\t\t\t\t\tsignal: new AbortController().signal,\n\t\t\t\t\tcustomInstructions: hookResult?.customInstructions ?? options?.customInstructions,\n\t\t\t\t\treplaceInstructions: hookResult?.replaceInstructions ?? options?.replaceInstructions,\n\t\t\t\t\t...createSummarizationRetry(\"branch_summary\", this.streamOptions.summarizationRetry, (event) =>\n\t\t\t\t\t\tthis.emitOwn(event),\n\t\t\t\t\t),\n\t\t\t\t});\n\t\t\t\tif (!branchSummary.ok) {\n\t\t\t\t\tif (branchSummary.error.code === \"aborted\") return { cancelled: true };\n\t\t\t\t\tthrow new AgentHarnessError(\"branch_summary\", branchSummary.error.message, branchSummary.error);\n\t\t\t\t}\n\t\t\t\tsummaryText = branchSummary.value.summary;\n\t\t\t\tsummaryDetails = {\n\t\t\t\t\treadFiles: branchSummary.value.readFiles,\n\t\t\t\t\tmodifiedFiles: branchSummary.value.modifiedFiles,\n\t\t\t\t};\n\t\t\t}\n\t\t\tlet editorText: string | undefined;\n\t\t\tlet newLeafId: string | null;\n\t\t\tif (targetEntry.type === \"message\" && targetEntry.message.role === \"user\") {\n\t\t\t\tnewLeafId = targetEntry.parentId;\n\t\t\t\tconst content = targetEntry.message.content;\n\t\t\t\teditorText =\n\t\t\t\t\ttypeof content === \"string\"\n\t\t\t\t\t\t? content\n\t\t\t\t\t\t: content\n\t\t\t\t\t\t\t\t.filter((c): c is { readonly type: \"text\"; readonly text: string } => c.type === \"text\")\n\t\t\t\t\t\t\t\t.map((c) => c.text)\n\t\t\t\t\t\t\t\t.join(\"\");\n\t\t\t} else if (targetEntry.type === \"custom_message\") {\n\t\t\t\tnewLeafId = targetEntry.parentId;\n\t\t\t\teditorText =\n\t\t\t\t\ttypeof targetEntry.content === \"string\"\n\t\t\t\t\t\t? targetEntry.content\n\t\t\t\t\t\t: targetEntry.content\n\t\t\t\t\t\t\t\t.filter((c): c is { readonly type: \"text\"; readonly text: string } => c.type === \"text\")\n\t\t\t\t\t\t\t\t.map((c) => c.text)\n\t\t\t\t\t\t\t\t.join(\"\");\n\t\t\t} else {\n\t\t\t\tnewLeafId = targetId;\n\t\t\t}\n\t\t\tconst summaryId = await this.session.moveTo(\n\t\t\t\tnewLeafId,\n\t\t\t\tsummaryText\n\t\t\t\t\t? { summary: summaryText, details: summaryDetails, fromHook: hookResult?.summary !== undefined }\n\t\t\t\t\t: undefined,\n\t\t\t);\n\t\t\tif (summaryId) {\n\t\t\t\tconst entry = await this.session.getEntry(summaryId);\n\t\t\t\tif (entry?.type === \"branch_summary\") summaryEntry = entry;\n\t\t\t}\n\t\t\tawait this.emitOwn({\n\t\t\t\ttype: \"session_tree\",\n\t\t\t\tnewLeafId: await this.session.getLeafId(),\n\t\t\t\toldLeafId,\n\t\t\t\tsummaryEntry,\n\t\t\t\tfromHook: hookResult?.summary !== undefined,\n\t\t\t});\n\t\t\treturn { cancelled: false, editorText, summaryEntry };\n\t\t} catch (error) {\n\t\t\tthrow normalizeHarnessError(error, \"branch_summary\");\n\t\t} finally {\n\t\t\tthis.phase = \"idle\";\n\t\t}\n\t}\n\n\tgetModel(): Model<any> {\n\t\treturn this.model;\n\t}\n\n\tasync setModel(model: Model<any>): Promise<void> {\n\t\ttry {\n\t\t\tconst previousModel = this.model;\n\t\t\tif (this.phase === \"idle\") {\n\t\t\t\tawait this.session.appendModelChange(model.provider, model.id);\n\t\t\t} else {\n\t\t\t\tthis.pendingSessionWrites.push({ type: \"model_change\", provider: model.provider, modelId: model.id });\n\t\t\t}\n\t\t\tthis.model = model;\n\t\t\tawait this.emitOwn({ type: \"model_update\", model, previousModel, source: \"set\" });\n\t\t} catch (error) {\n\t\t\tthrow normalizeHarnessError(error, \"session\");\n\t\t}\n\t}\n\n\tgetThinkingLevel(): ThinkingLevel {\n\t\treturn this.thinkingLevel;\n\t}\n\n\tasync setThinkingLevel(level: ThinkingLevel): Promise<void> {\n\t\ttry {\n\t\t\tconst previousLevel = this.thinkingLevel;\n\t\t\tif (this.phase === \"idle\") {\n\t\t\t\tawait this.session.appendThinkingLevelChange(level);\n\t\t\t} else {\n\t\t\t\tthis.pendingSessionWrites.push({ type: \"thinking_level_change\", thinkingLevel: level });\n\t\t\t}\n\t\t\tthis.thinkingLevel = level;\n\t\t\tawait this.emitOwn({ type: \"thinking_level_update\", level, previousLevel });\n\t\t} catch (error) {\n\t\t\tthrow normalizeHarnessError(error, \"session\");\n\t\t}\n\t}\n\n\tgetTools(): TTool[] {\n\t\treturn [...this.tools.values()];\n\t}\n\n\tasync setTools(tools: TTool[], activeToolNames?: string[]): Promise<void> {\n\t\ttry {\n\t\t\tthis.validateUniqueNames(\n\t\t\t\ttools.map((tool) => tool.name),\n\t\t\t\t\"Duplicate tool name(s)\",\n\t\t\t);\n\t\t\tconst nextTools = new Map(tools.map((tool) => [tool.name, tool]));\n\t\t\tconst nextActiveToolNames = activeToolNames ? [...activeToolNames] : this.activeToolNames;\n\t\t\tthis.validateToolNames(nextActiveToolNames, nextTools);\n\t\t\tconst previousToolNames = [...this.tools.keys()];\n\t\t\tconst previousActiveToolNames = [...this.activeToolNames];\n\t\t\tif (this.phase === \"idle\") {\n\t\t\t\tawait this.session.appendActiveToolsChange(nextActiveToolNames);\n\t\t\t} else {\n\t\t\t\tthis.pendingSessionWrites.push({ type: \"active_tools_change\", activeToolNames: [...nextActiveToolNames] });\n\t\t\t}\n\t\t\tthis.tools = nextTools;\n\t\t\tthis.activeToolNames = [...nextActiveToolNames];\n\t\t\tawait this.emitOwn({\n\t\t\t\ttype: \"tools_update\",\n\t\t\t\ttoolNames: [...this.tools.keys()],\n\t\t\t\tpreviousToolNames,\n\t\t\t\tactiveToolNames: [...this.activeToolNames],\n\t\t\t\tpreviousActiveToolNames,\n\t\t\t\tsource: \"set\",\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthrow normalizeHarnessError(error, \"invalid_argument\");\n\t\t}\n\t}\n\n\tgetActiveTools(): TTool[] {\n\t\treturn this.activeToolNames.map((name) => this.tools.get(name)!);\n\t}\n\n\tasync setActiveTools(toolNames: string[]): Promise<void> {\n\t\ttry {\n\t\t\tthis.validateToolNames(toolNames);\n\t\t\tconst previousToolNames = [...this.tools.keys()];\n\t\t\tconst previousActiveToolNames = [...this.activeToolNames];\n\t\t\tif (this.phase === \"idle\") {\n\t\t\t\tawait this.session.appendActiveToolsChange(toolNames);\n\t\t\t} else {\n\t\t\t\tthis.pendingSessionWrites.push({ type: \"active_tools_change\", activeToolNames: [...toolNames] });\n\t\t\t}\n\t\t\tthis.activeToolNames = [...toolNames];\n\t\t\tawait this.emitOwn({\n\t\t\t\ttype: \"tools_update\",\n\t\t\t\ttoolNames: [...this.tools.keys()],\n\t\t\t\tpreviousToolNames,\n\t\t\t\tactiveToolNames: [...this.activeToolNames],\n\t\t\t\tpreviousActiveToolNames,\n\t\t\t\tsource: \"set\",\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthrow normalizeHarnessError(error, \"invalid_argument\");\n\t\t}\n\t}\n\n\tgetSteeringMode(): QueueMode {\n\t\treturn this.steeringQueueMode;\n\t}\n\n\tasync setSteeringMode(mode: QueueMode): Promise<void> {\n\t\tthis.steeringQueueMode = mode;\n\t}\n\n\tgetFollowUpMode(): QueueMode {\n\t\treturn this.followUpQueueMode;\n\t}\n\n\tasync setFollowUpMode(mode: QueueMode): Promise<void> {\n\t\tthis.followUpQueueMode = mode;\n\t}\n\n\tgetResources(): AgentHarnessResources<TSkill, TPromptTemplate> {\n\t\treturn {\n\t\t\tskills: this.resources.skills?.slice(),\n\t\t\tpromptTemplates: this.resources.promptTemplates?.slice(),\n\t\t};\n\t}\n\n\tasync setResources(resources: AgentHarnessResources<TSkill, TPromptTemplate>): Promise<void> {\n\t\tconst previousResources = this.getResources();\n\t\tthis.resources = {\n\t\t\tskills: resources.skills?.slice(),\n\t\t\tpromptTemplates: resources.promptTemplates?.slice(),\n\t\t};\n\t\tawait this.emitOwn({ type: \"resources_update\", resources: this.getResources(), previousResources });\n\t}\n\n\tgetStreamOptions(): AgentHarnessStreamOptions {\n\t\treturn cloneStreamOptions(this.streamOptions);\n\t}\n\n\tasync setStreamOptions(streamOptions: AgentHarnessStreamOptions): Promise<void> {\n\t\tthis.streamOptions = cloneStreamOptions(streamOptions);\n\t}\n\n\tasync abort(): Promise<AbortResult> {\n\t\tif (this.phase !== \"idle\" && this.phase !== \"turn\")\n\t\t\tthrow new AgentHarnessError(\"invalid_state\", `Cannot abort during ${this.phase}`);\n\t\tconst clearedSteer = this.steerQueue.splice(0);\n\t\tconst clearedFollowUp = this.followUpQueue.splice(0);\n\t\tthis.runAbortController?.abort();\n\t\tconst errors: Error[] = [];\n\t\ttry {\n\t\t\tawait this.emitQueueUpdate();\n\t\t} catch (error) {\n\t\t\terrors.push(toError(error));\n\t\t}\n\t\ttry {\n\t\t\tawait this.waitForIdle();\n\t\t} catch (error) {\n\t\t\terrors.push(toError(error));\n\t\t}\n\t\ttry {\n\t\t\tawait this.emitOwn({ type: \"abort\", clearedSteer, clearedFollowUp });\n\t\t} catch (error) {\n\t\t\terrors.push(toError(error));\n\t\t}\n\t\tif (errors.length > 0) {\n\t\t\tconst cause = errors.length === 1 ? errors[0]! : new AggregateError(errors, \"Abort completed with errors\");\n\t\t\tthrow normalizeHarnessError(cause, \"hook\");\n\t\t}\n\t\treturn { clearedSteer, clearedFollowUp };\n\t}\n\n\tasync waitForIdle(): Promise<void> {\n\t\t// Runs can chain: a settled listener may start the next prompt() before the\n\t\t// previous run's promise resolves, so keep waiting until no run is active.\n\t\twhile (this.runPromise) {\n\t\t\tawait this.runPromise;\n\t\t}\n\t}\n\n\tsubscribe(\n\t\tlistener: (event: AgentHarnessEvent<TSkill, TPromptTemplate>, signal?: AbortSignal) => Promise<void> | void,\n\t): () => void {\n\t\tlet handlers = this.handlers.get(SUBSCRIBER_EVENT_TYPE);\n\t\tif (!handlers) {\n\t\t\thandlers = new Set();\n\t\t\tthis.handlers.set(SUBSCRIBER_EVENT_TYPE, handlers);\n\t\t}\n\t\thandlers.add(listener as AgentHarnessHandler);\n\t\treturn () => handlers!.delete(listener as AgentHarnessHandler);\n\t}\n\n\ton<TType extends keyof AgentHarnessEventResultMap>(\n\t\ttype: TType,\n\t\thandler: (\n\t\t\tevent: Extract<AgentHarnessOwnEvent, { type: TType }>,\n\t\t) => Promise<AgentHarnessEventResultMap[TType]> | AgentHarnessEventResultMap[TType],\n\t): () => void {\n\t\tlet handlers = this.handlers.get(type);\n\t\tif (!handlers) {\n\t\t\thandlers = new Set();\n\t\t\tthis.handlers.set(type, handlers);\n\t\t}\n\t\thandlers.add(handler as AgentHarnessHandler);\n\t\treturn () => handlers!.delete(handler as AgentHarnessHandler);\n\t}\n}\n"]}