omk-agent-core 0.98.2 → 0.98.3

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 (61) hide show
  1. package/CHANGELOG.md +644 -0
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +3 -5
  4. package/dist/agent.js.map +1 -1
  5. package/dist/effects/effect-journal.d.ts +43 -0
  6. package/dist/effects/effect-journal.d.ts.map +1 -0
  7. package/dist/effects/effect-journal.js +186 -0
  8. package/dist/effects/effect-journal.js.map +1 -0
  9. package/dist/effects/effect-recovery.d.ts +70 -0
  10. package/dist/effects/effect-recovery.d.ts.map +1 -0
  11. package/dist/effects/effect-recovery.js +120 -0
  12. package/dist/effects/effect-recovery.js.map +1 -0
  13. package/dist/effects/effect-transitions.d.ts +34 -0
  14. package/dist/effects/effect-transitions.d.ts.map +1 -0
  15. package/dist/effects/effect-transitions.js +148 -0
  16. package/dist/effects/effect-transitions.js.map +1 -0
  17. package/dist/effects/effect-types.d.ts +135 -0
  18. package/dist/effects/effect-types.d.ts.map +1 -0
  19. package/dist/effects/effect-types.js +32 -0
  20. package/dist/effects/effect-types.js.map +1 -0
  21. package/dist/harness/abort-delivery.d.ts +26 -0
  22. package/dist/harness/abort-delivery.d.ts.map +1 -0
  23. package/dist/harness/abort-delivery.js +36 -0
  24. package/dist/harness/abort-delivery.js.map +1 -0
  25. package/dist/harness/agent-harness.d.ts +18 -0
  26. package/dist/harness/agent-harness.d.ts.map +1 -1
  27. package/dist/harness/agent-harness.js +37 -28
  28. package/dist/harness/agent-harness.js.map +1 -1
  29. package/dist/harness/canonical-digest.d.ts +32 -0
  30. package/dist/harness/canonical-digest.d.ts.map +1 -0
  31. package/dist/harness/canonical-digest.js +164 -0
  32. package/dist/harness/canonical-digest.js.map +1 -0
  33. package/dist/harness/deferred-commands.d.ts +53 -0
  34. package/dist/harness/deferred-commands.d.ts.map +1 -0
  35. package/dist/harness/deferred-commands.js +96 -0
  36. package/dist/harness/deferred-commands.js.map +1 -0
  37. package/dist/harness/operation-outcome.d.ts +18 -10
  38. package/dist/harness/operation-outcome.d.ts.map +1 -1
  39. package/dist/harness/operation-outcome.js +71 -35
  40. package/dist/harness/operation-outcome.js.map +1 -1
  41. package/dist/harness/operation-trace-divergence.d.ts +60 -0
  42. package/dist/harness/operation-trace-divergence.d.ts.map +1 -0
  43. package/dist/harness/operation-trace-divergence.js +199 -0
  44. package/dist/harness/operation-trace-divergence.js.map +1 -0
  45. package/dist/harness/operation-trace.d.ts +134 -0
  46. package/dist/harness/operation-trace.d.ts.map +1 -0
  47. package/dist/harness/operation-trace.js +161 -0
  48. package/dist/harness/operation-trace.js.map +1 -0
  49. package/dist/harness/subscriber-fanout.d.ts +14 -1
  50. package/dist/harness/subscriber-fanout.d.ts.map +1 -1
  51. package/dist/harness/subscriber-fanout.js +25 -6
  52. package/dist/harness/subscriber-fanout.js.map +1 -1
  53. package/dist/index.d.ts +2 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +2 -0
  56. package/dist/index.js.map +1 -1
  57. package/dist/listener-delivery.d.ts +22 -0
  58. package/dist/listener-delivery.d.ts.map +1 -0
  59. package/dist/listener-delivery.js +36 -0
  60. package/dist/listener-delivery.js.map +1 -0
  61. package/package.json +4 -3
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Effect Journal V2 vocabulary: the durable identity and phase model for every
3
+ * side effect a harness operation performs.
4
+ *
5
+ * A side effect is not a tool result. The tool result says what the tool
6
+ * reported; the effect record says what the runtime committed to before,
7
+ * during, and after the action, so that after a crash the question "did it
8
+ * happen?" has one of three honest answers — committed, not committed, or
9
+ * unknown — instead of being overwritten by a retry.
10
+ *
11
+ * This module imports nothing and declares no behaviour. Legal phase moves
12
+ * live in `effect-transitions.ts`, the hash-chained journal in
13
+ * `effect-journal.ts`, recovery in `effect-recovery.ts`.
14
+ */
15
+ export declare const EFFECT_RECORD_SCHEMA_VERSION: 2;
16
+ /** The hash a journal's first record chains to. */
17
+ export declare const EFFECT_JOURNAL_GENESIS_HASH: string;
18
+ /**
19
+ * What the runtime may assume about re-executing the effect.
20
+ *
21
+ * | semantics | automatic recovery |
22
+ * | --------------- | ------------------------------------------------- |
23
+ * | `pure` | re-execute freely |
24
+ * | `idempotent` | re-execute with the same idempotency key |
25
+ * | `inspectable` | inspect the target, then decide |
26
+ * | `compensatable` | run the declared compensation |
27
+ * | `opaque` | never re-execute automatically; operator decides |
28
+ */
29
+ export type EffectSemantics = "pure" | "idempotent" | "inspectable" | "compensatable" | "opaque";
30
+ export type EffectPhase = "prepared" | "dispatched" | "observed_committed" | "observed_not_committed" | "commit_unknown" | "acknowledged" | "compensating" | "compensated" | "abandoned";
31
+ /** Phases after which the journal accepts no further transition for the effect. */
32
+ export declare const TERMINAL_EFFECT_PHASES: readonly EffectPhase[];
33
+ /** Phases whose external outcome is not yet known; a verified verdict needs this set empty. */
34
+ export declare const UNCERTAIN_EFFECT_PHASES: readonly EffectPhase[];
35
+ export interface EffectInspectionDescriptor {
36
+ readonly kind: string;
37
+ readonly targetDigest?: string;
38
+ readonly parameters?: Readonly<Record<string, string>>;
39
+ }
40
+ export interface EffectCompensationDescriptor {
41
+ readonly kind: string;
42
+ readonly parameters?: Readonly<Record<string, string>>;
43
+ }
44
+ /** Who performs the effect, under which operation, attempt, lane epoch, and process incarnation. */
45
+ export interface EffectIdentity {
46
+ readonly effectId: string;
47
+ readonly operationId: string;
48
+ readonly attemptId: string;
49
+ readonly laneId?: string;
50
+ readonly laneEpoch?: number;
51
+ readonly processIncarnation: string;
52
+ }
53
+ /** What the effect intends to do, committed before dispatch and constant for the effect's lifetime. */
54
+ export interface EffectIntent {
55
+ readonly semantics: EffectSemantics;
56
+ readonly capabilityDigest: string;
57
+ readonly intentDigest: string;
58
+ readonly idempotencyKey?: string;
59
+ readonly inspectDescriptor?: EffectInspectionDescriptor;
60
+ readonly compensationDescriptor?: EffectCompensationDescriptor;
61
+ }
62
+ /** One hash-chained journal entry: the effect's identity, intent, and phase at `sequence`. */
63
+ export interface EffectRecord extends EffectIdentity, EffectIntent {
64
+ readonly schemaVersion: typeof EFFECT_RECORD_SCHEMA_VERSION;
65
+ readonly phase: EffectPhase;
66
+ readonly sequence: number;
67
+ readonly timestamp: string;
68
+ readonly reasonCode?: string;
69
+ readonly previousRecordHash: string;
70
+ readonly recordHash: string;
71
+ }
72
+ export type EffectObservation = "committed" | "not_committed" | "unknown";
73
+ export type EffectCommand = {
74
+ readonly type: "prepare";
75
+ readonly identity: EffectIdentity;
76
+ readonly intent: EffectIntent;
77
+ readonly timestamp: string;
78
+ } | {
79
+ readonly type: "dispatch";
80
+ readonly effectId: string;
81
+ readonly timestamp: string;
82
+ } | {
83
+ readonly type: "observe";
84
+ readonly effectId: string;
85
+ readonly observation: EffectObservation;
86
+ readonly timestamp: string;
87
+ readonly reasonCode?: string;
88
+ } | {
89
+ readonly type: "acknowledge";
90
+ readonly effectId: string;
91
+ readonly timestamp: string;
92
+ } | {
93
+ readonly type: "resolve_unknown";
94
+ readonly effectId: string;
95
+ readonly inspection: "committed" | "not_committed";
96
+ readonly timestamp: string;
97
+ } | {
98
+ readonly type: "redispatch";
99
+ readonly effectId: string;
100
+ readonly timestamp: string;
101
+ } | {
102
+ readonly type: "compensate_begin";
103
+ readonly effectId: string;
104
+ readonly timestamp: string;
105
+ } | {
106
+ readonly type: "compensate_end";
107
+ readonly effectId: string;
108
+ readonly result: "compensated" | "unknown";
109
+ readonly timestamp: string;
110
+ } | {
111
+ readonly type: "abandon";
112
+ readonly effectId: string;
113
+ readonly reasonCode: string;
114
+ readonly timestamp: string;
115
+ };
116
+ export type EffectViolationCode = "unknown_effect" | "duplicate_effect" | "invalid_transition" | "unsafe_replay" | "missing_descriptor" | "identity_mismatch" | "sequence_violation" | "chain_break" | "hash_mismatch" | "invalid_record";
117
+ export declare class EffectJournalViolation extends Error {
118
+ readonly code: EffectViolationCode;
119
+ readonly effectId?: string;
120
+ constructor(code: EffectViolationCode, message: string, effectId?: string);
121
+ }
122
+ export type EffectJournalResult<T> = {
123
+ readonly ok: true;
124
+ readonly value: T;
125
+ } | {
126
+ readonly ok: false;
127
+ readonly error: EffectJournalViolation;
128
+ };
129
+ /** Latest record per effect plus the chain head; the whole thing is plain data. */
130
+ export interface EffectJournalState {
131
+ readonly headHash: string;
132
+ readonly lastSequence: number;
133
+ readonly effects: Readonly<Record<string, EffectRecord>>;
134
+ }
135
+ //# sourceMappingURL=effect-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"effect-types.d.ts","sourceRoot":"","sources":["../../src/effects/effect-types.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Effect Journal V2 vocabulary: the durable identity and phase model for every\n * side effect a harness operation performs.\n *\n * A side effect is not a tool result. The tool result says what the tool\n * reported; the effect record says what the runtime committed to before,\n * during, and after the action, so that after a crash the question \"did it\n * happen?\" has one of three honest answers — committed, not committed, or\n * unknown — instead of being overwritten by a retry.\n *\n * This module imports nothing and declares no behaviour. Legal phase moves\n * live in `effect-transitions.ts`, the hash-chained journal in\n * `effect-journal.ts`, recovery in `effect-recovery.ts`.\n */\n\nexport const EFFECT_RECORD_SCHEMA_VERSION = 2 as const;\n\n/** The hash a journal's first record chains to. */\nexport const EFFECT_JOURNAL_GENESIS_HASH = \"0\".repeat(64);\n\n/**\n * What the runtime may assume about re-executing the effect.\n *\n * | semantics | automatic recovery |\n * | --------------- | ------------------------------------------------- |\n * | `pure` | re-execute freely |\n * | `idempotent` | re-execute with the same idempotency key |\n * | `inspectable` | inspect the target, then decide |\n * | `compensatable` | run the declared compensation |\n * | `opaque` | never re-execute automatically; operator decides |\n */\nexport type EffectSemantics = \"pure\" | \"idempotent\" | \"inspectable\" | \"compensatable\" | \"opaque\";\n\nexport type EffectPhase =\n\t| \"prepared\"\n\t| \"dispatched\"\n\t| \"observed_committed\"\n\t| \"observed_not_committed\"\n\t| \"commit_unknown\"\n\t| \"acknowledged\"\n\t| \"compensating\"\n\t| \"compensated\"\n\t| \"abandoned\";\n\n/** Phases after which the journal accepts no further transition for the effect. */\nexport const TERMINAL_EFFECT_PHASES: readonly EffectPhase[] = [\"acknowledged\", \"compensated\", \"abandoned\"];\n\n/** Phases whose external outcome is not yet known; a verified verdict needs this set empty. */\nexport const UNCERTAIN_EFFECT_PHASES: readonly EffectPhase[] = [\"dispatched\", \"commit_unknown\", \"compensating\"];\n\nexport interface EffectInspectionDescriptor {\n\treadonly kind: string;\n\treadonly targetDigest?: string;\n\treadonly parameters?: Readonly<Record<string, string>>;\n}\n\nexport interface EffectCompensationDescriptor {\n\treadonly kind: string;\n\treadonly parameters?: Readonly<Record<string, string>>;\n}\n\n/** Who performs the effect, under which operation, attempt, lane epoch, and process incarnation. */\nexport interface EffectIdentity {\n\treadonly effectId: string;\n\treadonly operationId: string;\n\treadonly attemptId: string;\n\treadonly laneId?: string;\n\treadonly laneEpoch?: number;\n\treadonly processIncarnation: string;\n}\n\n/** What the effect intends to do, committed before dispatch and constant for the effect's lifetime. */\nexport interface EffectIntent {\n\treadonly semantics: EffectSemantics;\n\treadonly capabilityDigest: string;\n\treadonly intentDigest: string;\n\treadonly idempotencyKey?: string;\n\treadonly inspectDescriptor?: EffectInspectionDescriptor;\n\treadonly compensationDescriptor?: EffectCompensationDescriptor;\n}\n\n/** One hash-chained journal entry: the effect's identity, intent, and phase at `sequence`. */\nexport interface EffectRecord extends EffectIdentity, EffectIntent {\n\treadonly schemaVersion: typeof EFFECT_RECORD_SCHEMA_VERSION;\n\treadonly phase: EffectPhase;\n\treadonly sequence: number;\n\treadonly timestamp: string;\n\treadonly reasonCode?: string;\n\treadonly previousRecordHash: string;\n\treadonly recordHash: string;\n}\n\nexport type EffectObservation = \"committed\" | \"not_committed\" | \"unknown\";\n\nexport type EffectCommand =\n\t| {\n\t\t\treadonly type: \"prepare\";\n\t\t\treadonly identity: EffectIdentity;\n\t\t\treadonly intent: EffectIntent;\n\t\t\treadonly timestamp: string;\n\t }\n\t| { readonly type: \"dispatch\"; readonly effectId: string; readonly timestamp: string }\n\t| {\n\t\t\treadonly type: \"observe\";\n\t\t\treadonly effectId: string;\n\t\t\treadonly observation: EffectObservation;\n\t\t\treadonly timestamp: string;\n\t\t\treadonly reasonCode?: string;\n\t }\n\t| { readonly type: \"acknowledge\"; readonly effectId: string; readonly timestamp: string }\n\t| {\n\t\t\treadonly type: \"resolve_unknown\";\n\t\t\treadonly effectId: string;\n\t\t\treadonly inspection: \"committed\" | \"not_committed\";\n\t\t\treadonly timestamp: string;\n\t }\n\t| { readonly type: \"redispatch\"; readonly effectId: string; readonly timestamp: string }\n\t| { readonly type: \"compensate_begin\"; readonly effectId: string; readonly timestamp: string }\n\t| {\n\t\t\treadonly type: \"compensate_end\";\n\t\t\treadonly effectId: string;\n\t\t\treadonly result: \"compensated\" | \"unknown\";\n\t\t\treadonly timestamp: string;\n\t }\n\t| { readonly type: \"abandon\"; readonly effectId: string; readonly reasonCode: string; readonly timestamp: string };\n\nexport type EffectViolationCode =\n\t| \"unknown_effect\"\n\t| \"duplicate_effect\"\n\t| \"invalid_transition\"\n\t| \"unsafe_replay\"\n\t| \"missing_descriptor\"\n\t| \"identity_mismatch\"\n\t| \"sequence_violation\"\n\t| \"chain_break\"\n\t| \"hash_mismatch\"\n\t| \"invalid_record\";\n\nexport class EffectJournalViolation extends Error {\n\tpublic readonly code: EffectViolationCode;\n\tpublic readonly effectId?: string;\n\n\tconstructor(code: EffectViolationCode, message: string, effectId?: string) {\n\t\tsuper(message);\n\t\tthis.name = \"EffectJournalViolation\";\n\t\tthis.code = code;\n\t\tthis.effectId = effectId;\n\t}\n}\n\nexport type EffectJournalResult<T> =\n\t| { readonly ok: true; readonly value: T }\n\t| { readonly ok: false; readonly error: EffectJournalViolation };\n\n/** Latest record per effect plus the chain head; the whole thing is plain data. */\nexport interface EffectJournalState {\n\treadonly headHash: string;\n\treadonly lastSequence: number;\n\treadonly effects: Readonly<Record<string, EffectRecord>>;\n}\n"]}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Effect Journal V2 vocabulary: the durable identity and phase model for every
3
+ * side effect a harness operation performs.
4
+ *
5
+ * A side effect is not a tool result. The tool result says what the tool
6
+ * reported; the effect record says what the runtime committed to before,
7
+ * during, and after the action, so that after a crash the question "did it
8
+ * happen?" has one of three honest answers — committed, not committed, or
9
+ * unknown — instead of being overwritten by a retry.
10
+ *
11
+ * This module imports nothing and declares no behaviour. Legal phase moves
12
+ * live in `effect-transitions.ts`, the hash-chained journal in
13
+ * `effect-journal.ts`, recovery in `effect-recovery.ts`.
14
+ */
15
+ export const EFFECT_RECORD_SCHEMA_VERSION = 2;
16
+ /** The hash a journal's first record chains to. */
17
+ export const EFFECT_JOURNAL_GENESIS_HASH = "0".repeat(64);
18
+ /** Phases after which the journal accepts no further transition for the effect. */
19
+ export const TERMINAL_EFFECT_PHASES = ["acknowledged", "compensated", "abandoned"];
20
+ /** Phases whose external outcome is not yet known; a verified verdict needs this set empty. */
21
+ export const UNCERTAIN_EFFECT_PHASES = ["dispatched", "commit_unknown", "compensating"];
22
+ export class EffectJournalViolation extends Error {
23
+ code;
24
+ effectId;
25
+ constructor(code, message, effectId) {
26
+ super(message);
27
+ this.name = "EffectJournalViolation";
28
+ this.code = code;
29
+ this.effectId = effectId;
30
+ }
31
+ }
32
+ //# sourceMappingURL=effect-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"effect-types.js","sourceRoot":"","sources":["../../src/effects/effect-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAU,CAAC;AAEvD,mDAAmD;AACnD,MAAM,CAAC,MAAM,2BAA2B,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AA0B1D,mFAAmF;AACnF,MAAM,CAAC,MAAM,sBAAsB,GAA2B,CAAC,cAAc,EAAE,aAAa,EAAE,WAAW,CAAC,CAAC;AAE3G,+FAA+F;AAC/F,MAAM,CAAC,MAAM,uBAAuB,GAA2B,CAAC,YAAY,EAAE,gBAAgB,EAAE,cAAc,CAAC,CAAC;AA0FhH,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAChC,IAAI,CAAsB;IAC1B,QAAQ,CAAU;IAElC,YAAY,IAAyB,EAAE,OAAe,EAAE,QAAiB,EAAE;QAC1E,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;QACrC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAAA,CACzB;CACD","sourcesContent":["/**\n * Effect Journal V2 vocabulary: the durable identity and phase model for every\n * side effect a harness operation performs.\n *\n * A side effect is not a tool result. The tool result says what the tool\n * reported; the effect record says what the runtime committed to before,\n * during, and after the action, so that after a crash the question \"did it\n * happen?\" has one of three honest answers — committed, not committed, or\n * unknown — instead of being overwritten by a retry.\n *\n * This module imports nothing and declares no behaviour. Legal phase moves\n * live in `effect-transitions.ts`, the hash-chained journal in\n * `effect-journal.ts`, recovery in `effect-recovery.ts`.\n */\n\nexport const EFFECT_RECORD_SCHEMA_VERSION = 2 as const;\n\n/** The hash a journal's first record chains to. */\nexport const EFFECT_JOURNAL_GENESIS_HASH = \"0\".repeat(64);\n\n/**\n * What the runtime may assume about re-executing the effect.\n *\n * | semantics | automatic recovery |\n * | --------------- | ------------------------------------------------- |\n * | `pure` | re-execute freely |\n * | `idempotent` | re-execute with the same idempotency key |\n * | `inspectable` | inspect the target, then decide |\n * | `compensatable` | run the declared compensation |\n * | `opaque` | never re-execute automatically; operator decides |\n */\nexport type EffectSemantics = \"pure\" | \"idempotent\" | \"inspectable\" | \"compensatable\" | \"opaque\";\n\nexport type EffectPhase =\n\t| \"prepared\"\n\t| \"dispatched\"\n\t| \"observed_committed\"\n\t| \"observed_not_committed\"\n\t| \"commit_unknown\"\n\t| \"acknowledged\"\n\t| \"compensating\"\n\t| \"compensated\"\n\t| \"abandoned\";\n\n/** Phases after which the journal accepts no further transition for the effect. */\nexport const TERMINAL_EFFECT_PHASES: readonly EffectPhase[] = [\"acknowledged\", \"compensated\", \"abandoned\"];\n\n/** Phases whose external outcome is not yet known; a verified verdict needs this set empty. */\nexport const UNCERTAIN_EFFECT_PHASES: readonly EffectPhase[] = [\"dispatched\", \"commit_unknown\", \"compensating\"];\n\nexport interface EffectInspectionDescriptor {\n\treadonly kind: string;\n\treadonly targetDigest?: string;\n\treadonly parameters?: Readonly<Record<string, string>>;\n}\n\nexport interface EffectCompensationDescriptor {\n\treadonly kind: string;\n\treadonly parameters?: Readonly<Record<string, string>>;\n}\n\n/** Who performs the effect, under which operation, attempt, lane epoch, and process incarnation. */\nexport interface EffectIdentity {\n\treadonly effectId: string;\n\treadonly operationId: string;\n\treadonly attemptId: string;\n\treadonly laneId?: string;\n\treadonly laneEpoch?: number;\n\treadonly processIncarnation: string;\n}\n\n/** What the effect intends to do, committed before dispatch and constant for the effect's lifetime. */\nexport interface EffectIntent {\n\treadonly semantics: EffectSemantics;\n\treadonly capabilityDigest: string;\n\treadonly intentDigest: string;\n\treadonly idempotencyKey?: string;\n\treadonly inspectDescriptor?: EffectInspectionDescriptor;\n\treadonly compensationDescriptor?: EffectCompensationDescriptor;\n}\n\n/** One hash-chained journal entry: the effect's identity, intent, and phase at `sequence`. */\nexport interface EffectRecord extends EffectIdentity, EffectIntent {\n\treadonly schemaVersion: typeof EFFECT_RECORD_SCHEMA_VERSION;\n\treadonly phase: EffectPhase;\n\treadonly sequence: number;\n\treadonly timestamp: string;\n\treadonly reasonCode?: string;\n\treadonly previousRecordHash: string;\n\treadonly recordHash: string;\n}\n\nexport type EffectObservation = \"committed\" | \"not_committed\" | \"unknown\";\n\nexport type EffectCommand =\n\t| {\n\t\t\treadonly type: \"prepare\";\n\t\t\treadonly identity: EffectIdentity;\n\t\t\treadonly intent: EffectIntent;\n\t\t\treadonly timestamp: string;\n\t }\n\t| { readonly type: \"dispatch\"; readonly effectId: string; readonly timestamp: string }\n\t| {\n\t\t\treadonly type: \"observe\";\n\t\t\treadonly effectId: string;\n\t\t\treadonly observation: EffectObservation;\n\t\t\treadonly timestamp: string;\n\t\t\treadonly reasonCode?: string;\n\t }\n\t| { readonly type: \"acknowledge\"; readonly effectId: string; readonly timestamp: string }\n\t| {\n\t\t\treadonly type: \"resolve_unknown\";\n\t\t\treadonly effectId: string;\n\t\t\treadonly inspection: \"committed\" | \"not_committed\";\n\t\t\treadonly timestamp: string;\n\t }\n\t| { readonly type: \"redispatch\"; readonly effectId: string; readonly timestamp: string }\n\t| { readonly type: \"compensate_begin\"; readonly effectId: string; readonly timestamp: string }\n\t| {\n\t\t\treadonly type: \"compensate_end\";\n\t\t\treadonly effectId: string;\n\t\t\treadonly result: \"compensated\" | \"unknown\";\n\t\t\treadonly timestamp: string;\n\t }\n\t| { readonly type: \"abandon\"; readonly effectId: string; readonly reasonCode: string; readonly timestamp: string };\n\nexport type EffectViolationCode =\n\t| \"unknown_effect\"\n\t| \"duplicate_effect\"\n\t| \"invalid_transition\"\n\t| \"unsafe_replay\"\n\t| \"missing_descriptor\"\n\t| \"identity_mismatch\"\n\t| \"sequence_violation\"\n\t| \"chain_break\"\n\t| \"hash_mismatch\"\n\t| \"invalid_record\";\n\nexport class EffectJournalViolation extends Error {\n\tpublic readonly code: EffectViolationCode;\n\tpublic readonly effectId?: string;\n\n\tconstructor(code: EffectViolationCode, message: string, effectId?: string) {\n\t\tsuper(message);\n\t\tthis.name = \"EffectJournalViolation\";\n\t\tthis.code = code;\n\t\tthis.effectId = effectId;\n\t}\n}\n\nexport type EffectJournalResult<T> =\n\t| { readonly ok: true; readonly value: T }\n\t| { readonly ok: false; readonly error: EffectJournalViolation };\n\n/** Latest record per effect plus the chain head; the whole thing is plain data. */\nexport interface EffectJournalState {\n\treadonly headHash: string;\n\treadonly lastSequence: number;\n\treadonly effects: Readonly<Record<string, EffectRecord>>;\n}\n"]}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Pure abort-delivery vocabulary, extracted from `AgentHarness`.
3
+ *
4
+ * Aborting has two halves that callers need separately: delivering the signal
5
+ * (safe from anywhere, including an operation's own callbacks, because it never
6
+ * waits) and waiting for the target's settlement (which a callback of that same
7
+ * operation must never do — settlement awaits the callback). Keeping the refusal
8
+ * table and the delivery description here means the oversized harness module only
9
+ * gains thin delegation, and the rules stay testable without a harness instance.
10
+ */
11
+ import type { HarnessAbortCapture } from "./operation-lifecycle-controller.ts";
12
+ import type { HarnessLifecycleState } from "./operation-lifecycle-types.ts";
13
+ /** What one abort-signal delivery did, without waiting for anything. */
14
+ export interface AbortSignalDeliveryResult {
15
+ /** The operation the signal targeted, when one was active or settling. */
16
+ readonly operationId?: string;
17
+ /** True only when the abort signal was newly delivered to that operation. */
18
+ readonly signalDelivered: boolean;
19
+ /** True when the target was already settling, so no signal could be delivered. */
20
+ readonly alreadySettling: boolean;
21
+ }
22
+ /** Throw when the active operation is one that refuses an abort. */
23
+ export declare function assertAbortAllowed(snapshot: Readonly<HarnessLifecycleState>): void;
24
+ /** Describe one captured abort delivery for a public, wait-free result. */
25
+ export declare function describeAbortDelivery(capture: HarnessAbortCapture): AbortSignalDeliveryResult;
26
+ //# sourceMappingURL=abort-delivery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abort-delivery.d.ts","sourceRoot":"","sources":["../../src/harness/abort-delivery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC/E,OAAO,KAAK,EAAE,qBAAqB,EAAwB,MAAM,gCAAgC,CAAC;AAElG,wEAAwE;AACxE,MAAM,WAAW,yBAAyB;IACzC,0EAA0E;IAC1E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,6EAA6E;IAC7E,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,kFAAkF;IAClF,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;CAClC;AAWD,oEAAoE;AACpE,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,CAAC,qBAAqB,CAAC,GAAG,IAAI,CAIlF;AAED,2EAA2E;AAC3E,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,mBAAmB,GAAG,yBAAyB,CAM7F","sourcesContent":["/**\n * Pure abort-delivery vocabulary, extracted from `AgentHarness`.\n *\n * Aborting has two halves that callers need separately: delivering the signal\n * (safe from anywhere, including an operation's own callbacks, because it never\n * waits) and waiting for the target's settlement (which a callback of that same\n * operation must never do — settlement awaits the callback). Keeping the refusal\n * table and the delivery description here means the oversized harness module only\n * gains thin delegation, and the rules stay testable without a harness instance.\n */\n\nimport { AgentHarnessError } from \"./errors.ts\";\nimport type { HarnessAbortCapture } from \"./operation-lifecycle-controller.ts\";\nimport type { HarnessLifecycleState, HarnessOperationKind } from \"./operation-lifecycle-types.ts\";\n\n/** What one abort-signal delivery did, without waiting for anything. */\nexport interface AbortSignalDeliveryResult {\n\t/** The operation the signal targeted, when one was active or settling. */\n\treadonly operationId?: string;\n\t/** True only when the abort signal was newly delivered to that operation. */\n\treadonly signalDelivered: boolean;\n\t/** True when the target was already settling, so no signal could be delivered. */\n\treadonly alreadySettling: boolean;\n}\n\n/**\n * Operations that refuse an abort: cancelling them mid-flight would leave their\n * work half-applied, so the caller gets an explicit `invalid_state` instead.\n */\nconst ABORT_REFUSED_OPERATIONS: ReadonlyMap<HarnessOperationKind, string> = new Map([\n\t[\"manual_compaction\", \"Cannot abort during compaction\"],\n\t[\"tree_navigation\", \"Cannot abort during branch_summary\"],\n]);\n\n/** Throw when the active operation is one that refuses an abort. */\nexport function assertAbortAllowed(snapshot: Readonly<HarnessLifecycleState>): void {\n\tif (snapshot.tag !== \"active\") return;\n\tconst refused = ABORT_REFUSED_OPERATIONS.get(snapshot.operation.kind);\n\tif (refused !== undefined) throw new AgentHarnessError(\"invalid_state\", refused);\n}\n\n/** Describe one captured abort delivery for a public, wait-free result. */\nexport function describeAbortDelivery(capture: HarnessAbortCapture): AbortSignalDeliveryResult {\n\treturn {\n\t\t...(capture.target === undefined ? {} : { operationId: capture.target.operation.operationId }),\n\t\tsignalDelivered: capture.signalDelivered,\n\t\talreadySettling: capture.target !== undefined && !capture.signalDelivered,\n\t};\n}\n"]}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Pure abort-delivery vocabulary, extracted from `AgentHarness`.
3
+ *
4
+ * Aborting has two halves that callers need separately: delivering the signal
5
+ * (safe from anywhere, including an operation's own callbacks, because it never
6
+ * waits) and waiting for the target's settlement (which a callback of that same
7
+ * operation must never do — settlement awaits the callback). Keeping the refusal
8
+ * table and the delivery description here means the oversized harness module only
9
+ * gains thin delegation, and the rules stay testable without a harness instance.
10
+ */
11
+ import { AgentHarnessError } from "./errors.js";
12
+ /**
13
+ * Operations that refuse an abort: cancelling them mid-flight would leave their
14
+ * work half-applied, so the caller gets an explicit `invalid_state` instead.
15
+ */
16
+ const ABORT_REFUSED_OPERATIONS = new Map([
17
+ ["manual_compaction", "Cannot abort during compaction"],
18
+ ["tree_navigation", "Cannot abort during branch_summary"],
19
+ ]);
20
+ /** Throw when the active operation is one that refuses an abort. */
21
+ export function assertAbortAllowed(snapshot) {
22
+ if (snapshot.tag !== "active")
23
+ return;
24
+ const refused = ABORT_REFUSED_OPERATIONS.get(snapshot.operation.kind);
25
+ if (refused !== undefined)
26
+ throw new AgentHarnessError("invalid_state", refused);
27
+ }
28
+ /** Describe one captured abort delivery for a public, wait-free result. */
29
+ export function describeAbortDelivery(capture) {
30
+ return {
31
+ ...(capture.target === undefined ? {} : { operationId: capture.target.operation.operationId }),
32
+ signalDelivered: capture.signalDelivered,
33
+ alreadySettling: capture.target !== undefined && !capture.signalDelivered,
34
+ };
35
+ }
36
+ //# sourceMappingURL=abort-delivery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abort-delivery.js","sourceRoot":"","sources":["../../src/harness/abort-delivery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAchD;;;GAGG;AACH,MAAM,wBAAwB,GAA8C,IAAI,GAAG,CAAC;IACnF,CAAC,mBAAmB,EAAE,gCAAgC,CAAC;IACvD,CAAC,iBAAiB,EAAE,oCAAoC,CAAC;CACzD,CAAC,CAAC;AAEH,oEAAoE;AACpE,MAAM,UAAU,kBAAkB,CAAC,QAAyC,EAAQ;IACnF,IAAI,QAAQ,CAAC,GAAG,KAAK,QAAQ;QAAE,OAAO;IACtC,MAAM,OAAO,GAAG,wBAAwB,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACtE,IAAI,OAAO,KAAK,SAAS;QAAE,MAAM,IAAI,iBAAiB,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;AAAA,CACjF;AAED,2EAA2E;AAC3E,MAAM,UAAU,qBAAqB,CAAC,OAA4B,EAA6B;IAC9F,OAAO;QACN,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC;QAC9F,eAAe,EAAE,OAAO,CAAC,eAAe;QACxC,eAAe,EAAE,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,OAAO,CAAC,eAAe;KACzE,CAAC;AAAA,CACF","sourcesContent":["/**\n * Pure abort-delivery vocabulary, extracted from `AgentHarness`.\n *\n * Aborting has two halves that callers need separately: delivering the signal\n * (safe from anywhere, including an operation's own callbacks, because it never\n * waits) and waiting for the target's settlement (which a callback of that same\n * operation must never do — settlement awaits the callback). Keeping the refusal\n * table and the delivery description here means the oversized harness module only\n * gains thin delegation, and the rules stay testable without a harness instance.\n */\n\nimport { AgentHarnessError } from \"./errors.ts\";\nimport type { HarnessAbortCapture } from \"./operation-lifecycle-controller.ts\";\nimport type { HarnessLifecycleState, HarnessOperationKind } from \"./operation-lifecycle-types.ts\";\n\n/** What one abort-signal delivery did, without waiting for anything. */\nexport interface AbortSignalDeliveryResult {\n\t/** The operation the signal targeted, when one was active or settling. */\n\treadonly operationId?: string;\n\t/** True only when the abort signal was newly delivered to that operation. */\n\treadonly signalDelivered: boolean;\n\t/** True when the target was already settling, so no signal could be delivered. */\n\treadonly alreadySettling: boolean;\n}\n\n/**\n * Operations that refuse an abort: cancelling them mid-flight would leave their\n * work half-applied, so the caller gets an explicit `invalid_state` instead.\n */\nconst ABORT_REFUSED_OPERATIONS: ReadonlyMap<HarnessOperationKind, string> = new Map([\n\t[\"manual_compaction\", \"Cannot abort during compaction\"],\n\t[\"tree_navigation\", \"Cannot abort during branch_summary\"],\n]);\n\n/** Throw when the active operation is one that refuses an abort. */\nexport function assertAbortAllowed(snapshot: Readonly<HarnessLifecycleState>): void {\n\tif (snapshot.tag !== \"active\") return;\n\tconst refused = ABORT_REFUSED_OPERATIONS.get(snapshot.operation.kind);\n\tif (refused !== undefined) throw new AgentHarnessError(\"invalid_state\", refused);\n}\n\n/** Describe one captured abort delivery for a public, wait-free result. */\nexport function describeAbortDelivery(capture: HarnessAbortCapture): AbortSignalDeliveryResult {\n\treturn {\n\t\t...(capture.target === undefined ? {} : { operationId: capture.target.operation.operationId }),\n\t\tsignalDelivered: capture.signalDelivered,\n\t\talreadySettling: capture.target !== undefined && !capture.signalDelivered,\n\t};\n}\n"]}
@@ -1,5 +1,7 @@
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 AbortSignalDeliveryResult } from "./abort-delivery.ts";
4
+ import { type CommandRef, type DeferredHarnessCommand } from "./deferred-commands.ts";
3
5
  import type { AbortResult, AgentHarnessEvent, AgentHarnessEventResultMap, AgentHarnessOptions, AgentHarnessOwnEvent, AgentHarnessResources, AgentHarnessStreamOptions, CompactResult, ExecutionEnv, HarnessSession, NavigateTreeResult, PromptTemplate, Skill } from "./types.ts";
4
6
  export declare class AgentHarness<TSkill extends Skill = Skill, TPromptTemplate extends PromptTemplate = PromptTemplate, TTool extends AgentTool = AgentTool> {
5
7
  readonly env: ExecutionEnv;
@@ -23,6 +25,7 @@ export declare class AgentHarness<TSkill extends Skill = Skill, TPromptTemplate
23
25
  private nextTurnQueue;
24
26
  private handlers;
25
27
  private readonly subscribers;
28
+ private readonly deferredCommands;
26
29
  constructor(options: AgentHarnessOptions<TSkill, TPromptTemplate, TTool>);
27
30
  private emitOwn;
28
31
  private emitAny;
@@ -100,6 +103,21 @@ export declare class AgentHarness<TSkill extends Skill = Skill, TPromptTemplate
100
103
  setResources(resources: AgentHarnessResources<TSkill, TPromptTemplate>): Promise<void>;
101
104
  getStreamOptions(): AgentHarnessStreamOptions;
102
105
  setStreamOptions(streamOptions: AgentHarnessStreamOptions): Promise<void>;
106
+ /**
107
+ * Deliver the abort signal to the current operation without waiting for it to
108
+ * settle. Safe from an operation's own callbacks: nothing here awaits
109
+ * settlement, so it cannot form the cycle `abort()` has to refuse.
110
+ */
111
+ requestAbort(): AbortSignalDeliveryResult;
112
+ /**
113
+ * Queue work to run once the harness is idle and return its ref immediately.
114
+ *
115
+ * This is the callback-safe way to schedule follow-up work: awaiting
116
+ * `waitForIdle()` or `abort()` from a callback of the operation being settled
117
+ * deadlocks, because settlement awaits that callback. The ref reports the
118
+ * command's outcome and cancels it while it is still queued.
119
+ */
120
+ runWhenIdle(command: DeferredHarnessCommand): Promise<CommandRef>;
103
121
  abort(): Promise<AbortResult>;
104
122
  waitForIdle(): Promise<void>;
105
123
  subscribe(listener: (event: AgentHarnessEvent<TSkill, TPromptTemplate>, signal?: AbortSignal) => Promise<void> | void): () => void;
@@ -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 {\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 } 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 {\n\ttype AttemptLease,\n\ttype OperationLease,\n\tOperationLifecycleController,\n} from \"./operation-lifecycle-controller.ts\";\nimport {\n\ttype HarnessAttemptOutcome,\n\ttype HarnessAttemptReason,\n\ttype HarnessOperationKind,\n\ttype HarnessOperationOutcome,\n\tPROMPT_FAMILY_KINDS,\n} from \"./operation-lifecycle-types.ts\";\nimport {\n\tclassifyAssistantOutcome,\n\tclassifyAttemptFailure,\n\tclassifyAttemptOutcome,\n\tclassifyNavigateTreeOutcome,\n\tcombineBoundaryErrors,\n\tnormalizeHarnessError,\n\tresolveOperationFailure,\n\tresolveOperationOutcome,\n} from \"./operation-outcome.ts\";\nimport { formatPromptTemplateInvocation } from \"./prompt-templates.ts\";\nimport { uuidv7 } from \"./session/uuid.ts\";\nimport { type QueuedSessionWrite, SessionWriteCoordinator } from \"./session-write-coordinator.ts\";\nimport { formatSkillInvocation } from \"./skills.ts\";\nimport { applyStreamOptionsPatch, cloneStreamOptions, mergeHeaders } from \"./stream-options.ts\";\nimport { SubscriberFanout } from \"./subscriber-fanout.ts\";\nimport { createSummarizationRetry } from \"./summarization-retry.ts\";\nimport { resolveNavigationTarget, runBranchSummary } from \"./tree-navigation.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\tPromptTemplate,\n\tSession,\n\tSkill,\n} from \"./types.ts\";\nimport { AgentHarnessError, toError } from \"./types.ts\";\n\ntype AgentHarnessHandler = (event: any, signal?: AbortSignal) => Promise<any> | any;\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 readonly lifecycle: OperationLifecycleController;\n\tprivate readonly sessionWrites: SessionWriteCoordinator<AgentMessage>;\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\tprivate readonly subscribers = new SubscriberFanout<AgentHarnessEvent<TSkill, TPromptTemplate>>();\n\n\tconstructor(options: AgentHarnessOptions<TSkill, TPromptTemplate, TTool>) {\n\t\tthis.env = options.env;\n\t\tthis.session = options.session;\n\t\tthis.sessionWrites = new SessionWriteCoordinator(this.session);\n\t\tthis.lifecycle = new OperationLifecycleController({\n\t\t\tcreateOperationId: () => uuidv7(),\n\t\t\tnow: () => Date.now(),\n\t\t});\n\t\tthis.sessionFacade = new HarnessSessionFacade(this.session, () => this.currentPhase(), this.sessionWrites);\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\tawait this.emitAny(event as AgentHarnessEvent<TSkill, TPromptTemplate>, signal);\n\t}\n\n\t/** Subscriber fan-out; the self-wait barrier lives in `SubscriberFanout`. */\n\tprivate async emitAny(event: AgentHarnessEvent<TSkill, TPromptTemplate>, signal?: AbortSignal): Promise<void> {\n\t\tawait this.subscribers.emit(event, this.lifecycle.getCurrentOperation()?.operationId, signal);\n\t}\n\n\t/** Fail closed when an awaited listener tries to wait on its own operation. */\n\tprivate rejectCurrentOperationSelfWait(api: string): void {\n\t\tthis.subscribers.assertNotSelfWait(api, this.lifecycle.getCurrentOperation()?.operationId);\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\t/**\n\t * Facade write-gate vocabulary mapped from lifecycle state. `settling` maps\n\t * to \"idle\": the queue is drained by the settlement finalizer first, and\n\t * listener writes persist after it through the coordinator tail.\n\t */\n\tprivate currentPhase(): AgentHarnessPhase {\n\t\tconst snapshot = this.lifecycle.getSnapshot();\n\t\tif (snapshot.tag !== \"active\") return \"idle\";\n\t\tswitch (snapshot.operation.kind) {\n\t\t\tcase \"manual_compaction\":\n\t\t\t\treturn \"compaction\";\n\t\t\tcase \"tree_navigation\":\n\t\t\t\treturn \"branch_summary\";\n\t\t\tdefault:\n\t\t\t\treturn \"turn\";\n\t\t}\n\t}\n\n\t/** Config writes persist immediately outside an active operation and queue during one. */\n\tprivate async persistConfigChange(write: QueuedSessionWrite<AgentMessage>): Promise<void> {\n\t\tif (this.lifecycle.getSnapshot().tag !== \"active\") {\n\t\t\tawait this.sessionWrites.persistAfterPending(write);\n\t\t} else {\n\t\t\tthis.sessionWrites.enqueue(write);\n\t\t}\n\t}\n\n\t/**\n\t * Single wrapper for every public operation: begin a lease, run the body,\n\t * then settle exactly once. The final flush and the `settled` event happen\n\t * inside the settling barrier; a finalizer failure never reports success.\n\t */\n\tprivate async runOperation<T>(\n\t\tkind: HarnessOperationKind,\n\t\tfallbackCode: AgentHarnessError[\"code\"],\n\t\tbody: (lease: OperationLease) => Promise<T>,\n\t\tclassifyResult?: (result: T) => HarnessOperationOutcome | undefined,\n\t): Promise<T> {\n\t\tconst lease = this.lifecycle.begin(kind);\n\t\tlet result: T | undefined;\n\t\tlet bodyError: unknown;\n\t\t// Everything after a successful begin() runs inside one capture region. A\n\t\t// throwing `operation_started` listener must not escape before settle(),\n\t\t// or the lifecycle would stay active and wedge the harness at \"busy\".\n\t\ttry {\n\t\t\tawait this.emitOwn({ type: \"operation_started\", operation: lease.operation });\n\t\t\tresult = await body(lease);\n\t\t} catch (error) {\n\t\t\tbodyError = error;\n\t\t}\n\t\t// The final flush precedes classification: a persistence failure after a\n\t\t// provider success must never record or report a completed operation.\n\t\tlet flushError: unknown;\n\t\ttry {\n\t\t\tawait this.sessionWrites.flush();\n\t\t} catch (error) {\n\t\t\tflushError = error;\n\t\t}\n\t\tconst outcome = resolveOperationOutcome({\n\t\t\tsignalAborted: lease.signal.aborted,\n\t\t\tresult,\n\t\t\tbodyError,\n\t\t\tflushError,\n\t\t\tclassifyResult,\n\t\t\tfallbackCode,\n\t\t});\n\t\tlet settleError: unknown;\n\t\ttry {\n\t\t\tawait this.lifecycle.settle(lease, outcome, async () => {\n\t\t\t\tawait this.emitOwn(\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"settled\",\n\t\t\t\t\t\tnextTurnCount: this.nextTurnQueue.length,\n\t\t\t\t\t\toperationId: lease.operation.operationId,\n\t\t\t\t\t\toutcome,\n\t\t\t\t\t\tattemptCount: this.lifecycle.getAttemptSummaries(lease).length,\n\t\t\t\t\t},\n\t\t\t\t\tlease.signal,\n\t\t\t\t);\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tsettleError = error;\n\t\t}\n\t\tconst failure = resolveOperationFailure({ bodyError, flushError, settleError, fallbackCode });\n\t\tif (failure !== undefined) throw failure;\n\t\treturn result as 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\tlease?: OperationLease,\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.sessionWrites.flush();\n\t\t\t\tif (lease) {\n\t\t\t\t\tconst snapshot = this.lifecycle.getSnapshot();\n\t\t\t\t\tif (snapshot.tag === \"active\" && snapshot.stage === \"save_point\") {\n\t\t\t\t\t\tthis.lifecycle.setStage(lease, \"attempt_running\");\n\t\t\t\t\t}\n\t\t\t\t}\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 handleAgentEvent(event: AgentEvent, signal?: AbortSignal, lease?: OperationLease): 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\t// The flush runs even after a failing listener so accepted writes are\n\t\t\t// not stranded; a failing flush must then report next to that listener\n\t\t\t// error, not in place of it.\n\t\t\tconst hadPendingMutations = this.sessionWrites.hasPending();\n\t\t\tlet flushError: unknown;\n\t\t\ttry {\n\t\t\t\tawait this.sessionWrites.flush();\n\t\t\t} catch (error) {\n\t\t\t\tflushError = error;\n\t\t\t}\n\t\t\tif (lease) {\n\t\t\t\tconst snapshot = this.lifecycle.getSnapshot();\n\t\t\t\tif (snapshot.tag === \"active\" && snapshot.stage === \"attempt_running\") {\n\t\t\t\t\tthis.lifecycle.setStage(lease, \"save_point\");\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst failure = combineBoundaryErrors(\n\t\t\t\t[eventError, flushError],\n\t\t\t\t\"turn_end listener failed and the save-point flush failed\",\n\t\t\t\t\"hook\",\n\t\t\t);\n\t\t\tif (failure !== undefined) throw failure;\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// agent_end is an attempt event: flush its accepted writes, but lifecycle\n\t\t\t// settlement and the settled event belong to OperationLease.settle().\n\t\t\tawait this.sessionWrites.flush();\n\t\t\tawait this.emitAny(event, 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\tlease?: OperationLease,\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, lease);\n\t\tawait this.handleAgentEvent({ type: \"message_end\", message: failureMessage }, signal, lease);\n\t\tawait this.handleAgentEvent({ type: \"turn_end\", message: failureMessage, toolResults: [] }, signal, lease);\n\t\tawait this.handleAgentEvent({ type: \"agent_end\", messages }, signal, lease);\n\t\treturn messages;\n\t}\n\n\tprivate async executeTurn(\n\t\tlease: OperationLease,\n\t\tturnState: AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>,\n\t\ttext: string,\n\t\toptions?: { images?: ImageContent[] },\n\t): Promise<AssistantMessage> {\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\tlease,\n\t\t\t\"initial\",\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(lease, result);\n\t}\n\n\t/**\n\t * Sole attempt boundary: begin, announce, run, classify, close, announce, flush.\n\t *\n\t * Once `beginAttempt()` succeeds the attempt is closed exactly once on every\n\t * path, so `count(attempt_started) == count(attempt_finished)` holds even when\n\t * the `attempt_started` observer throws. `attempt_finished` is emitted only\n\t * after the attempt is already closed, so a throwing observer can fail the\n\t * operation but can never reopen committed attempt state. The closing flush\n\t * is not a `finally`: a `finally` that awaits a throwing flush would replace\n\t * the body error, hiding the provider or listener failure from the audit trail.\n\t */\n\tprivate async runAttempt<T>(\n\t\tlease: OperationLease,\n\t\treason: HarnessAttemptReason,\n\t\tbody: (attempt: AttemptLease) => Promise<T>,\n\t\tclassify: (result: T) => HarnessAttemptOutcome,\n\t): Promise<T> {\n\t\tconst attemptLease = this.lifecycle.beginAttempt(lease, reason);\n\t\tlet result: T | undefined;\n\t\tlet bodyError: unknown;\n\t\ttry {\n\t\t\tawait this.emitOwn({ type: \"attempt_started\", attempt: attemptLease.attempt }, lease.signal);\n\t\t\tresult = await body(attemptLease);\n\t\t} catch (error) {\n\t\t\tbodyError = error;\n\t\t}\n\t\tconst outcome: HarnessAttemptOutcome =\n\t\t\tbodyError === undefined ? classify(result as T) : classifyAttemptFailure(bodyError);\n\t\tthis.lifecycle.finishAttempt(lease, attemptLease, outcome);\n\t\tlet observerError: unknown;\n\t\ttry {\n\t\t\tawait this.emitOwn(\n\t\t\t\t{ type: \"attempt_finished\", summary: this.lifecycle.getAttemptSummary(lease, attemptLease) },\n\t\t\t\tlease.signal,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tobserverError = error;\n\t\t}\n\t\tlet flushError: unknown;\n\t\ttry {\n\t\t\tawait this.sessionWrites.flush();\n\t\t} catch (error) {\n\t\t\tflushError = error;\n\t\t}\n\t\tconst failure = combineBoundaryErrors(\n\t\t\t[bodyError, observerError, flushError],\n\t\t\t\"Attempt failed and its attempt_finished reporting or closing flush failed\",\n\t\t\t\"unknown\",\n\t\t);\n\t\tif (failure !== undefined) throw failure;\n\t\treturn result as T;\n\t}\n\n\tprivate async executeAgentRun(\n\t\tlease: OperationLease,\n\t\treason: HarnessAttemptReason,\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\treturn await this.runAttempt(\n\t\t\tlease,\n\t\t\treason,\n\t\t\tasync (attemptLease) => {\n\t\t\t\tconst signal = attemptLease.signal;\n\t\t\t\tconst getTurnState = () => activeTurnState;\n\t\t\t\tconst setTurnState = (nextTurnState: AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>) => {\n\t\t\t\t\tactiveTurnState = nextTurnState;\n\t\t\t\t};\n\t\t\t\tconst completedMessages: AgentMessage[] = [];\n\t\t\t\tconst emit = async (event: AgentEvent): Promise<void> => {\n\t\t\t\t\tif (event.type === \"message_end\") completedMessages.push(event.message);\n\t\t\t\t\tawait this.handleAgentEvent(event, signal, lease);\n\t\t\t\t};\n\t\t\t\tlet newMessages: AgentMessage[];\n\t\t\t\ttry {\n\t\t\t\t\tconst loopConfig = this.createLoopConfig(getTurnState, setTurnState, lease);\n\t\t\t\t\tconst streamFn = this.createStreamFn(getTurnState);\n\t\t\t\t\tnewMessages = initialMessages\n\t\t\t\t\t\t? await runAgentLoop(initialMessages, context, loopConfig, emit, signal, streamFn)\n\t\t\t\t\t\t: await runAgentLoopContinue(context, loopConfig, emit, signal, streamFn);\n\t\t\t\t} catch (error) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tnewMessages = await this.emitRunFailure(\n\t\t\t\t\t\t\tactiveTurnState.model,\n\t\t\t\t\t\t\terror,\n\t\t\t\t\t\t\tsignal.aborted,\n\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t\tcompletedMessages,\n\t\t\t\t\t\t\tlease,\n\t\t\t\t\t\t);\n\t\t\t\t\t} catch (failureError) {\n\t\t\t\t\t\tconst cause = new AggregateError(\n\t\t\t\t\t\t\t[toError(error), toError(failureError)],\n\t\t\t\t\t\t\t\"Agent run failed and failure reporting failed\",\n\t\t\t\t\t\t);\n\t\t\t\t\t\tthrow new AgentHarnessError(\"unknown\", cause.message, cause);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor (let i = newMessages.length - 1; i >= 0; i--) {\n\t\t\t\t\tconst message = newMessages[i]!;\n\t\t\t\t\tif (message.role === \"assistant\") return message;\n\t\t\t\t}\n\t\t\t\tthrow new AgentHarnessError(\"invalid_state\", \"AgentHarness prompt completed without an assistant message\");\n\t\t\t},\n\t\t\t(message) => classifyAttemptOutcome(message, activeTurnState.model.contextWindow),\n\t\t);\n\t}\n\n\t/**\n\t * One-shot overflow recovery inside the originating operation. The lease is\n\t * proof that this operation still owns the harness, so no run-ownership or\n\t * phase re-check is needed; a strict lifecycle makes a newer operation\n\t * starting mid-recovery impossible.\n\t */\n\tprivate async recoverContextOverflow(lease: OperationLease, message: AssistantMessage): Promise<AssistantMessage> {\n\t\tif (!this.compactionSettings.enabled || !isContextOverflow(message, this.model.contextWindow)) {\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.lifecycle.setStage(lease, \"recovering_overflow\");\n\t\ttry {\n\t\t\tconst compacted = await this.runCompaction({ automatic: true, signal: lease.signal });\n\t\t\tif (!compacted) {\n\t\t\t\tawait this.session.moveTo(leafId);\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(\n\t\t\t\tlease,\n\t\t\t\t\"context_overflow_recovery\",\n\t\t\t\tturnState,\n\t\t\t\tthis.createContext(turnState),\n\t\t\t);\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\treturn this.runOperation(\n\t\t\t\"prompt\",\n\t\t\t\"unknown\",\n\t\t\tasync (lease) => {\n\t\t\t\tconst turnState = await this.createTurnState();\n\t\t\t\treturn await this.executeTurn(lease, turnState, text, options);\n\t\t\t},\n\t\t\tclassifyAssistantOutcome,\n\t\t);\n\t}\n\n\tasync skill(name: string, additionalInstructions?: string): Promise<AssistantMessage> {\n\t\treturn this.runOperation(\n\t\t\t\"skill\",\n\t\t\t\"unknown\",\n\t\t\tasync (lease) => {\n\t\t\t\tconst turnState = await this.createTurnState();\n\t\t\t\tconst skill = (turnState.resources.skills ?? []).find((candidate) => candidate.name === name);\n\t\t\t\tif (!skill) throw new AgentHarnessError(\"invalid_argument\", `Unknown skill: ${name}`);\n\t\t\t\treturn await this.executeTurn(lease, turnState, formatSkillInvocation(skill, additionalInstructions));\n\t\t\t},\n\t\t\tclassifyAssistantOutcome,\n\t\t);\n\t}\n\n\tasync promptFromTemplate(name: string, args: string[] = []): Promise<AssistantMessage> {\n\t\treturn this.runOperation(\n\t\t\t\"prompt_template\",\n\t\t\t\"unknown\",\n\t\t\tasync (lease) => {\n\t\t\t\tconst turnState = await this.createTurnState();\n\t\t\t\tconst template = (turnState.resources.promptTemplates ?? []).find((candidate) => candidate.name === name);\n\t\t\t\tif (!template) throw new AgentHarnessError(\"invalid_argument\", `Unknown prompt template: ${name}`);\n\t\t\t\treturn await this.executeTurn(lease, turnState, formatPromptTemplateInvocation(template, args));\n\t\t\t},\n\t\t\tclassifyAssistantOutcome,\n\t\t);\n\t}\n\n\t/**\n\t * Steering and follow-up input is consumed only by a running agent attempt.\n\t * A structural operation (`compact`, `navigateTree`) runs none, so accepting\n\t * input there would silently inject it into an unrelated later prompt.\n\t */\n\tprivate expectQueueConsumer(action: string): void {\n\t\tconst snapshot = this.lifecycle.getSnapshot();\n\t\tif (snapshot.tag !== \"active\") throw new AgentHarnessError(\"invalid_state\", `Cannot ${action} while idle`);\n\t\tif (!PROMPT_FAMILY_KINDS.includes(snapshot.operation.kind)) {\n\t\t\tthrow new AgentHarnessError(\n\t\t\t\t\"invalid_state\",\n\t\t\t\t`Cannot ${action} during ${snapshot.operation.kind}: no agent attempt can consume it`,\n\t\t\t);\n\t\t}\n\t}\n\n\tasync steer(text: string, options?: { images?: ImageContent[] }): Promise<void> {\n\t\tthis.expectQueueConsumer(\"steer\");\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\tthis.expectQueueConsumer(\"follow up\");\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\toptions.beforeCommit?.();\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\treturn this.runOperation(\"manual_compaction\", \"compaction\", async (lease) => {\n\t\t\tthis.lifecycle.setStage(lease, \"structural_running\");\n\t\t\tconst result = await this.runCompaction({\n\t\t\t\tautomatic: false,\n\t\t\t\tcustomInstructions,\n\t\t\t\tbeforeCommit: () => {\n\t\t\t\t\tthis.lifecycle.setStage(lease, \"committing\");\n\t\t\t\t},\n\t\t\t});\n\t\t\tif (!result) throw new AgentHarnessError(\"compaction\", \"Nothing to compact\");\n\t\t\treturn result;\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\treturn this.runOperation(\n\t\t\t\"tree_navigation\",\n\t\t\t\"branch_summary\",\n\t\t\tasync (lease) => {\n\t\t\t\tthis.lifecycle.setStage(lease, \"structural_running\");\n\t\t\t\tconst oldLeafId = await this.session.getLeafId();\n\t\t\t\t// No-op navigation mutates nothing, so it completes without ever\n\t\t\t\t// entering the `committing` stage.\n\t\t\t\tif (oldLeafId === targetId) return { cancelled: false };\n\t\t\t\tconst targetEntry = await this.session.getEntry(targetId);\n\t\t\t\tif (!targetEntry) throw new AgentHarnessError(\"invalid_argument\", `Entry ${targetId} not found`);\n\t\t\t\tconst { entries, commonAncestorId } = await collectEntriesForBranchSummary(\n\t\t\t\t\tthis.session,\n\t\t\t\t\toldLeafId,\n\t\t\t\t\ttargetId,\n\t\t\t\t);\n\t\t\t\tconst preparation = {\n\t\t\t\t\ttargetId,\n\t\t\t\t\toldLeafId,\n\t\t\t\t\tcommonAncestorId,\n\t\t\t\t\tentriesToSummarize: entries,\n\t\t\t\t\tuserWantsSummary: options?.summarize ?? false,\n\t\t\t\t\tcustomInstructions: options?.customInstructions,\n\t\t\t\t\treplaceInstructions: options?.replaceInstructions,\n\t\t\t\t\tlabel: options?.label,\n\t\t\t\t};\n\t\t\t\tconst signal = new AbortController().signal;\n\t\t\t\tconst hookResult = await this.emitHook({ type: \"session_before_tree\", preparation, signal });\n\t\t\t\tif (hookResult?.cancel) return { cancelled: true };\n\t\t\t\tlet summaryEntry: NavigateTreeResult[\"summaryEntry\"];\n\t\t\t\tlet summaryText: string | undefined = hookResult?.summary?.summary;\n\t\t\t\tlet summaryDetails: unknown = hookResult?.summary?.details;\n\t\t\t\tif (!summaryText && options?.summarize && entries.length > 0) {\n\t\t\t\t\tconst model = this.model;\n\t\t\t\t\tif (!model) throw new AgentHarnessError(\"invalid_state\", \"No model set for branch summary\");\n\t\t\t\t\tconst auth = await this.getApiKeyAndHeaders?.(model);\n\t\t\t\t\tif (!auth) throw new AgentHarnessError(\"auth\", \"No auth available for branch summary\");\n\t\t\t\t\tconst branchSummary = await runBranchSummary({\n\t\t\t\t\t\tentries,\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\tapiKey: auth.apiKey,\n\t\t\t\t\t\theaders: auth.headers,\n\t\t\t\t\t\tcustomInstructions: hookResult?.customInstructions ?? options?.customInstructions,\n\t\t\t\t\t\treplaceInstructions: hookResult?.replaceInstructions ?? options?.replaceInstructions,\n\t\t\t\t\t\tsummarizationRetry: this.streamOptions.summarizationRetry,\n\t\t\t\t\t\temit: (event) => this.emitOwn(event),\n\t\t\t\t\t});\n\t\t\t\t\tif (branchSummary.cancelled) return { cancelled: true };\n\t\t\t\t\tsummaryText = branchSummary.summary;\n\t\t\t\t\tsummaryDetails = branchSummary.details;\n\t\t\t\t}\n\t\t\t\tconst { newLeafId, editorText } = resolveNavigationTarget(targetEntry, targetId);\n\t\t\t\t// Single declared commit point of a tree navigation.\n\t\t\t\tthis.lifecycle.setStage(lease, \"committing\");\n\t\t\t\tconst summaryId = await this.session.moveTo(\n\t\t\t\t\tnewLeafId,\n\t\t\t\t\tsummaryText\n\t\t\t\t\t\t? { summary: summaryText, details: summaryDetails, fromHook: hookResult?.summary !== undefined }\n\t\t\t\t\t\t: undefined,\n\t\t\t\t);\n\t\t\t\tif (summaryId) {\n\t\t\t\t\tconst entry = await this.session.getEntry(summaryId);\n\t\t\t\t\tif (entry?.type === \"branch_summary\") summaryEntry = entry;\n\t\t\t\t}\n\t\t\t\tawait this.emitOwn({\n\t\t\t\t\ttype: \"session_tree\",\n\t\t\t\t\tnewLeafId: await this.session.getLeafId(),\n\t\t\t\t\toldLeafId,\n\t\t\t\t\tsummaryEntry,\n\t\t\t\t\tfromHook: hookResult?.summary !== undefined,\n\t\t\t\t});\n\t\t\t\treturn { cancelled: false, editorText, summaryEntry };\n\t\t\t},\n\t\t\tclassifyNavigateTreeOutcome,\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\tconst nextProvider = model.provider;\n\t\t\tconst nextModelId = model.id;\n\t\t\tawait this.persistConfigChange({ type: \"model_change\", provider: nextProvider, modelId: nextModelId });\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\tawait this.persistConfigChange({ type: \"thinking_level_change\", thinkingLevel: level });\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\tawait this.persistConfigChange({ type: \"active_tools_change\", activeToolNames: [...nextActiveToolNames] });\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\tconst nextActiveToolNames = [...toolNames];\n\t\t\tthis.validateToolNames(nextActiveToolNames);\n\t\t\tconst previousToolNames = [...this.tools.keys()];\n\t\t\tconst previousActiveToolNames = [...this.activeToolNames];\n\t\t\tawait this.persistConfigChange({ type: \"active_tools_change\", activeToolNames: [...nextActiveToolNames] });\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\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\t// Aborting awaits the captured operation's settlement, so a listener of that\n\t\t// same operation must never reach the wait below.\n\t\tthis.rejectCurrentOperationSelfWait(\"abort()\");\n\t\tconst snapshot = this.lifecycle.getSnapshot();\n\t\tif (snapshot.tag === \"active\" && snapshot.operation.kind === \"manual_compaction\") {\n\t\t\tthrow new AgentHarnessError(\"invalid_state\", \"Cannot abort during compaction\");\n\t\t}\n\t\tif (snapshot.tag === \"active\" && snapshot.operation.kind === \"tree_navigation\") {\n\t\t\tthrow new AgentHarnessError(\"invalid_state\", \"Cannot abort during branch_summary\");\n\t\t}\n\t\t// Capture the current operation before delivering any signal: an operation\n\t\t// started later by a settlement listener is never this call's target.\n\t\tconst capture = this.lifecycle.requestAbort();\n\t\tconst clearedSteer = this.steerQueue.splice(0);\n\t\tconst clearedFollowUp = this.followUpQueue.splice(0);\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\tif (capture.target) await capture.target.settled;\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\tthis.rejectCurrentOperationSelfWait(\"waitForIdle()\");\n\t\t// Delegates to the lifecycle: resolves once no operation is active or settling.\n\t\tawait this.lifecycle.waitForIdle();\n\t}\n\n\tsubscribe(\n\t\tlistener: (event: AgentHarnessEvent<TSkill, TPromptTemplate>, signal?: AbortSignal) => Promise<void> | void,\n\t): () => void {\n\t\treturn this.subscribers.subscribe(listener);\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 { type AbortSignalDeliveryResult, assertAbortAllowed, describeAbortDelivery } from \"./abort-delivery.ts\";\nimport { collectEntriesForBranchSummary } 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 { type CommandRef, DeferredCommandQueue, type DeferredHarnessCommand } from \"./deferred-commands.ts\";\nimport { HarnessSessionFacade } from \"./harness-session.ts\";\nimport { convertToLlm, createFailureMessage, createUserMessage } from \"./messages.ts\";\nimport { findDuplicateNames } from \"./name-validation.ts\";\nimport {\n\ttype AttemptLease,\n\ttype OperationLease,\n\tOperationLifecycleController,\n} from \"./operation-lifecycle-controller.ts\";\nimport {\n\ttype HarnessAttemptOutcome,\n\ttype HarnessAttemptReason,\n\ttype HarnessOperationKind,\n\ttype HarnessOperationOutcome,\n\tPROMPT_FAMILY_KINDS,\n} from \"./operation-lifecycle-types.ts\";\nimport {\n\tclassifyAssistantOutcome,\n\tclassifyAttemptFailure,\n\tclassifyAttemptOutcome,\n\tclassifyNavigateTreeOutcome,\n\tcollectStepErrors,\n\tcombineBoundaryErrors,\n\tnormalizeHarnessError,\n\tresolveOperationFailure,\n\tresolveOperationOutcome,\n} from \"./operation-outcome.ts\";\nimport { formatPromptTemplateInvocation } from \"./prompt-templates.ts\";\nimport { uuidv7 } from \"./session/uuid.ts\";\nimport { type QueuedSessionWrite, SessionWriteCoordinator } from \"./session-write-coordinator.ts\";\nimport { formatSkillInvocation } from \"./skills.ts\";\nimport { applyStreamOptionsPatch, cloneStreamOptions, mergeHeaders } from \"./stream-options.ts\";\nimport { SubscriberFanout } from \"./subscriber-fanout.ts\";\nimport { createSummarizationRetry } from \"./summarization-retry.ts\";\nimport { resolveNavigationTarget, runBranchSummary } from \"./tree-navigation.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\tPromptTemplate,\n\tSession,\n\tSkill,\n} from \"./types.ts\";\nimport { AgentHarnessError, toError } from \"./types.ts\";\n\ntype AgentHarnessHandler = (event: any, signal?: AbortSignal) => Promise<any> | any;\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 readonly lifecycle: OperationLifecycleController;\n\tprivate readonly sessionWrites: SessionWriteCoordinator<AgentMessage>;\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\tprivate readonly subscribers = new SubscriberFanout<AgentHarnessEvent<TSkill, TPromptTemplate>>();\n\tprivate readonly deferredCommands = new DeferredCommandQueue(() => this.lifecycle.getSnapshot().tag === \"idle\");\n\n\tconstructor(options: AgentHarnessOptions<TSkill, TPromptTemplate, TTool>) {\n\t\tthis.env = options.env;\n\t\tthis.session = options.session;\n\t\tthis.sessionWrites = new SessionWriteCoordinator(this.session);\n\t\tthis.lifecycle = new OperationLifecycleController({\n\t\t\tcreateOperationId: () => uuidv7(),\n\t\t\tnow: () => Date.now(),\n\t\t});\n\t\tthis.sessionFacade = new HarnessSessionFacade(this.session, () => this.currentPhase(), this.sessionWrites);\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\tawait this.emitAny(event as AgentHarnessEvent<TSkill, TPromptTemplate>, signal);\n\t}\n\n\t/** Subscriber fan-out; the self-wait barrier lives in `SubscriberFanout`. */\n\tprivate async emitAny(event: AgentHarnessEvent<TSkill, TPromptTemplate>, signal?: AbortSignal): Promise<void> {\n\t\tawait this.subscribers.emit(event, this.lifecycle.getCurrentOperation()?.operationId, signal);\n\t}\n\n\t/** Fail closed when an awaited listener tries to wait on its own operation. */\n\tprivate rejectCurrentOperationSelfWait(api: string): void {\n\t\tthis.subscribers.assertNotSelfWait(api, this.lifecycle.getCurrentOperation()?.operationId);\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\t/**\n\t * Facade write-gate vocabulary mapped from lifecycle state. `settling` maps\n\t * to \"idle\": the queue is drained by the settlement finalizer first, and\n\t * listener writes persist after it through the coordinator tail.\n\t */\n\tprivate currentPhase(): AgentHarnessPhase {\n\t\tconst snapshot = this.lifecycle.getSnapshot();\n\t\tif (snapshot.tag !== \"active\") return \"idle\";\n\t\tswitch (snapshot.operation.kind) {\n\t\t\tcase \"manual_compaction\":\n\t\t\t\treturn \"compaction\";\n\t\t\tcase \"tree_navigation\":\n\t\t\t\treturn \"branch_summary\";\n\t\t\tdefault:\n\t\t\t\treturn \"turn\";\n\t\t}\n\t}\n\n\t/** Config writes persist immediately outside an active operation and queue during one. */\n\tprivate async persistConfigChange(write: QueuedSessionWrite<AgentMessage>): Promise<void> {\n\t\tif (this.lifecycle.getSnapshot().tag !== \"active\") {\n\t\t\tawait this.sessionWrites.persistAfterPending(write);\n\t\t} else {\n\t\t\tthis.sessionWrites.enqueue(write);\n\t\t}\n\t}\n\n\t/**\n\t * Single wrapper for every public operation: begin a lease, run the body,\n\t * then settle exactly once. The final flush and the `settled` event happen\n\t * inside the settling barrier; a finalizer failure never reports success.\n\t */\n\tprivate async runOperation<T>(\n\t\tkind: HarnessOperationKind,\n\t\tfallbackCode: AgentHarnessError[\"code\"],\n\t\tbody: (lease: OperationLease) => Promise<T>,\n\t\tclassifyResult?: (result: T) => HarnessOperationOutcome | undefined,\n\t): Promise<T> {\n\t\tconst lease = this.lifecycle.begin(kind);\n\t\tlet result: T | undefined;\n\t\tlet bodyError: unknown;\n\t\t// Everything after a successful begin() runs inside one capture region. A\n\t\t// throwing `operation_started` listener must not escape before settle(),\n\t\t// or the lifecycle would stay active and wedge the harness at \"busy\".\n\t\ttry {\n\t\t\tawait this.emitOwn({ type: \"operation_started\", operation: lease.operation });\n\t\t\tresult = await body(lease);\n\t\t} catch (error) {\n\t\t\tbodyError = error;\n\t\t}\n\t\t// The final flush precedes classification: a persistence failure after a\n\t\t// provider success must never record or report a completed operation.\n\t\tlet flushError: unknown;\n\t\ttry {\n\t\t\tawait this.sessionWrites.flush();\n\t\t} catch (error) {\n\t\t\tflushError = error;\n\t\t}\n\t\tconst outcome = resolveOperationOutcome({\n\t\t\tsignalAborted: lease.signal.aborted,\n\t\t\tresult,\n\t\t\tbodyError,\n\t\t\tflushError,\n\t\t\tclassifyResult,\n\t\t\tfallbackCode,\n\t\t});\n\t\tlet settleError: unknown;\n\t\ttry {\n\t\t\tawait this.lifecycle.settle(lease, outcome, async () => {\n\t\t\t\tawait this.emitOwn(\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"settled\",\n\t\t\t\t\t\tnextTurnCount: this.nextTurnQueue.length,\n\t\t\t\t\t\toperationId: lease.operation.operationId,\n\t\t\t\t\t\toutcome,\n\t\t\t\t\t\tattemptCount: this.lifecycle.getAttemptSummaries(lease).length,\n\t\t\t\t\t},\n\t\t\t\t\tlease.signal,\n\t\t\t\t);\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tsettleError = error;\n\t\t}\n\t\t// Deferred commands registered from this operation's callbacks run now that\n\t\t// the lifecycle is idle. Not awaited: this call's result must not depend on\n\t\t// work a listener scheduled.\n\t\tvoid this.deferredCommands.drain();\n\t\tconst failure = resolveOperationFailure({ bodyError, flushError, settleError, fallbackCode });\n\t\tif (failure !== undefined) throw failure;\n\t\treturn result as 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\tlease?: OperationLease,\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.sessionWrites.flush();\n\t\t\t\tif (lease) {\n\t\t\t\t\tconst snapshot = this.lifecycle.getSnapshot();\n\t\t\t\t\tif (snapshot.tag === \"active\" && snapshot.stage === \"save_point\") {\n\t\t\t\t\t\tthis.lifecycle.setStage(lease, \"attempt_running\");\n\t\t\t\t\t}\n\t\t\t\t}\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 handleAgentEvent(event: AgentEvent, signal?: AbortSignal, lease?: OperationLease): 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\t// The flush runs even after a failing listener so accepted writes are\n\t\t\t// not stranded; a failing flush must then report next to that listener\n\t\t\t// error, not in place of it.\n\t\t\tconst hadPendingMutations = this.sessionWrites.hasPending();\n\t\t\tlet flushError: unknown;\n\t\t\ttry {\n\t\t\t\tawait this.sessionWrites.flush();\n\t\t\t} catch (error) {\n\t\t\t\tflushError = error;\n\t\t\t}\n\t\t\tif (lease) {\n\t\t\t\tconst snapshot = this.lifecycle.getSnapshot();\n\t\t\t\tif (snapshot.tag === \"active\" && snapshot.stage === \"attempt_running\") {\n\t\t\t\t\tthis.lifecycle.setStage(lease, \"save_point\");\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst failure = combineBoundaryErrors(\n\t\t\t\t[eventError, flushError],\n\t\t\t\t\"turn_end listener failed and the save-point flush failed\",\n\t\t\t\t\"hook\",\n\t\t\t);\n\t\t\tif (failure !== undefined) throw failure;\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// agent_end is an attempt event: flush its accepted writes, but lifecycle\n\t\t\t// settlement and the settled event belong to OperationLease.settle().\n\t\t\tawait this.sessionWrites.flush();\n\t\t\tawait this.emitAny(event, 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\tlease?: OperationLease,\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, lease);\n\t\tawait this.handleAgentEvent({ type: \"message_end\", message: failureMessage }, signal, lease);\n\t\tawait this.handleAgentEvent({ type: \"turn_end\", message: failureMessage, toolResults: [] }, signal, lease);\n\t\tawait this.handleAgentEvent({ type: \"agent_end\", messages }, signal, lease);\n\t\treturn messages;\n\t}\n\n\tprivate async executeTurn(\n\t\tlease: OperationLease,\n\t\tturnState: AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>,\n\t\ttext: string,\n\t\toptions?: { images?: ImageContent[] },\n\t): Promise<AssistantMessage> {\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\tlease,\n\t\t\t\"initial\",\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(lease, result);\n\t}\n\n\t/**\n\t * Sole attempt boundary: begin, announce, run, classify, close, announce, flush.\n\t *\n\t * Once `beginAttempt()` succeeds the attempt is closed exactly once on every\n\t * path, so `count(attempt_started) == count(attempt_finished)` holds even when\n\t * the `attempt_started` observer throws. `attempt_finished` is emitted only\n\t * after the attempt is already closed, so a throwing observer can fail the\n\t * operation but can never reopen committed attempt state. The closing flush\n\t * is not a `finally`: a `finally` that awaits a throwing flush would replace\n\t * the body error, hiding the provider or listener failure from the audit trail.\n\t */\n\tprivate async runAttempt<T>(\n\t\tlease: OperationLease,\n\t\treason: HarnessAttemptReason,\n\t\tbody: (attempt: AttemptLease) => Promise<T>,\n\t\tclassify: (result: T) => HarnessAttemptOutcome,\n\t): Promise<T> {\n\t\tconst attemptLease = this.lifecycle.beginAttempt(lease, reason);\n\t\tlet result: T | undefined;\n\t\tlet bodyError: unknown;\n\t\ttry {\n\t\t\tawait this.emitOwn({ type: \"attempt_started\", attempt: attemptLease.attempt }, lease.signal);\n\t\t\tresult = await body(attemptLease);\n\t\t} catch (error) {\n\t\t\tbodyError = error;\n\t\t}\n\t\tconst outcome: HarnessAttemptOutcome =\n\t\t\tbodyError === undefined ? classify(result as T) : classifyAttemptFailure(bodyError);\n\t\tthis.lifecycle.finishAttempt(lease, attemptLease, outcome);\n\t\tlet observerError: unknown;\n\t\ttry {\n\t\t\tawait this.emitOwn(\n\t\t\t\t{ type: \"attempt_finished\", summary: this.lifecycle.getAttemptSummary(lease, attemptLease) },\n\t\t\t\tlease.signal,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tobserverError = error;\n\t\t}\n\t\tlet flushError: unknown;\n\t\ttry {\n\t\t\tawait this.sessionWrites.flush();\n\t\t} catch (error) {\n\t\t\tflushError = error;\n\t\t}\n\t\tconst failure = combineBoundaryErrors(\n\t\t\t[bodyError, observerError, flushError],\n\t\t\t\"Attempt failed and its attempt_finished reporting or closing flush failed\",\n\t\t\t\"unknown\",\n\t\t);\n\t\tif (failure !== undefined) throw failure;\n\t\treturn result as T;\n\t}\n\n\tprivate async executeAgentRun(\n\t\tlease: OperationLease,\n\t\treason: HarnessAttemptReason,\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\treturn await this.runAttempt(\n\t\t\tlease,\n\t\t\treason,\n\t\t\tasync (attemptLease) => {\n\t\t\t\tconst signal = attemptLease.signal;\n\t\t\t\tconst getTurnState = () => activeTurnState;\n\t\t\t\tconst setTurnState = (nextTurnState: AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>) => {\n\t\t\t\t\tactiveTurnState = nextTurnState;\n\t\t\t\t};\n\t\t\t\tconst completedMessages: AgentMessage[] = [];\n\t\t\t\tconst emit = async (event: AgentEvent): Promise<void> => {\n\t\t\t\t\tif (event.type === \"message_end\") completedMessages.push(event.message);\n\t\t\t\t\tawait this.handleAgentEvent(event, signal, lease);\n\t\t\t\t};\n\t\t\t\tlet newMessages: AgentMessage[];\n\t\t\t\ttry {\n\t\t\t\t\tconst loopConfig = this.createLoopConfig(getTurnState, setTurnState, lease);\n\t\t\t\t\tconst streamFn = this.createStreamFn(getTurnState);\n\t\t\t\t\tnewMessages = initialMessages\n\t\t\t\t\t\t? await runAgentLoop(initialMessages, context, loopConfig, emit, signal, streamFn)\n\t\t\t\t\t\t: await runAgentLoopContinue(context, loopConfig, emit, signal, streamFn);\n\t\t\t\t} catch (error) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tnewMessages = await this.emitRunFailure(\n\t\t\t\t\t\t\tactiveTurnState.model,\n\t\t\t\t\t\t\terror,\n\t\t\t\t\t\t\tsignal.aborted,\n\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t\tcompletedMessages,\n\t\t\t\t\t\t\tlease,\n\t\t\t\t\t\t);\n\t\t\t\t\t} catch (failureError) {\n\t\t\t\t\t\tconst cause = new AggregateError(\n\t\t\t\t\t\t\t[toError(error), toError(failureError)],\n\t\t\t\t\t\t\t\"Agent run failed and failure reporting failed\",\n\t\t\t\t\t\t);\n\t\t\t\t\t\tthrow new AgentHarnessError(\"unknown\", cause.message, cause);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor (let i = newMessages.length - 1; i >= 0; i--) {\n\t\t\t\t\tconst message = newMessages[i]!;\n\t\t\t\t\tif (message.role === \"assistant\") return message;\n\t\t\t\t}\n\t\t\t\tthrow new AgentHarnessError(\"invalid_state\", \"AgentHarness prompt completed without an assistant message\");\n\t\t\t},\n\t\t\t(message) => classifyAttemptOutcome(message, activeTurnState.model.contextWindow),\n\t\t);\n\t}\n\n\t/**\n\t * One-shot overflow recovery inside the originating operation. The lease is\n\t * proof that this operation still owns the harness, so no run-ownership or\n\t * phase re-check is needed; a strict lifecycle makes a newer operation\n\t * starting mid-recovery impossible.\n\t */\n\tprivate async recoverContextOverflow(lease: OperationLease, message: AssistantMessage): Promise<AssistantMessage> {\n\t\tif (!this.compactionSettings.enabled || !isContextOverflow(message, this.model.contextWindow)) {\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.lifecycle.setStage(lease, \"recovering_overflow\");\n\t\ttry {\n\t\t\tconst compacted = await this.runCompaction({ automatic: true, signal: lease.signal });\n\t\t\tif (!compacted) {\n\t\t\t\tawait this.session.moveTo(leafId);\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(\n\t\t\t\tlease,\n\t\t\t\t\"context_overflow_recovery\",\n\t\t\t\tturnState,\n\t\t\t\tthis.createContext(turnState),\n\t\t\t);\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\treturn this.runOperation(\n\t\t\t\"prompt\",\n\t\t\t\"unknown\",\n\t\t\tasync (lease) => {\n\t\t\t\tconst turnState = await this.createTurnState();\n\t\t\t\treturn await this.executeTurn(lease, turnState, text, options);\n\t\t\t},\n\t\t\tclassifyAssistantOutcome,\n\t\t);\n\t}\n\n\tasync skill(name: string, additionalInstructions?: string): Promise<AssistantMessage> {\n\t\treturn this.runOperation(\n\t\t\t\"skill\",\n\t\t\t\"unknown\",\n\t\t\tasync (lease) => {\n\t\t\t\tconst turnState = await this.createTurnState();\n\t\t\t\tconst skill = (turnState.resources.skills ?? []).find((candidate) => candidate.name === name);\n\t\t\t\tif (!skill) throw new AgentHarnessError(\"invalid_argument\", `Unknown skill: ${name}`);\n\t\t\t\treturn await this.executeTurn(lease, turnState, formatSkillInvocation(skill, additionalInstructions));\n\t\t\t},\n\t\t\tclassifyAssistantOutcome,\n\t\t);\n\t}\n\n\tasync promptFromTemplate(name: string, args: string[] = []): Promise<AssistantMessage> {\n\t\treturn this.runOperation(\n\t\t\t\"prompt_template\",\n\t\t\t\"unknown\",\n\t\t\tasync (lease) => {\n\t\t\t\tconst turnState = await this.createTurnState();\n\t\t\t\tconst template = (turnState.resources.promptTemplates ?? []).find((candidate) => candidate.name === name);\n\t\t\t\tif (!template) throw new AgentHarnessError(\"invalid_argument\", `Unknown prompt template: ${name}`);\n\t\t\t\treturn await this.executeTurn(lease, turnState, formatPromptTemplateInvocation(template, args));\n\t\t\t},\n\t\t\tclassifyAssistantOutcome,\n\t\t);\n\t}\n\n\t/**\n\t * Steering and follow-up input is consumed only by a running agent attempt.\n\t * A structural operation (`compact`, `navigateTree`) runs none, so accepting\n\t * input there would silently inject it into an unrelated later prompt.\n\t */\n\tprivate expectQueueConsumer(action: string): void {\n\t\tconst snapshot = this.lifecycle.getSnapshot();\n\t\tif (snapshot.tag !== \"active\") throw new AgentHarnessError(\"invalid_state\", `Cannot ${action} while idle`);\n\t\tif (!PROMPT_FAMILY_KINDS.includes(snapshot.operation.kind)) {\n\t\t\tthrow new AgentHarnessError(\n\t\t\t\t\"invalid_state\",\n\t\t\t\t`Cannot ${action} during ${snapshot.operation.kind}: no agent attempt can consume it`,\n\t\t\t);\n\t\t}\n\t}\n\n\tasync steer(text: string, options?: { images?: ImageContent[] }): Promise<void> {\n\t\tthis.expectQueueConsumer(\"steer\");\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\tthis.expectQueueConsumer(\"follow up\");\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\toptions.beforeCommit?.();\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\treturn this.runOperation(\"manual_compaction\", \"compaction\", async (lease) => {\n\t\t\tthis.lifecycle.setStage(lease, \"structural_running\");\n\t\t\tconst result = await this.runCompaction({\n\t\t\t\tautomatic: false,\n\t\t\t\tcustomInstructions,\n\t\t\t\tbeforeCommit: () => {\n\t\t\t\t\tthis.lifecycle.setStage(lease, \"committing\");\n\t\t\t\t},\n\t\t\t});\n\t\t\tif (!result) throw new AgentHarnessError(\"compaction\", \"Nothing to compact\");\n\t\t\treturn result;\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\treturn this.runOperation(\n\t\t\t\"tree_navigation\",\n\t\t\t\"branch_summary\",\n\t\t\tasync (lease) => {\n\t\t\t\tthis.lifecycle.setStage(lease, \"structural_running\");\n\t\t\t\tconst oldLeafId = await this.session.getLeafId();\n\t\t\t\t// No-op navigation mutates nothing, so it completes without ever\n\t\t\t\t// entering the `committing` stage.\n\t\t\t\tif (oldLeafId === targetId) return { cancelled: false };\n\t\t\t\tconst targetEntry = await this.session.getEntry(targetId);\n\t\t\t\tif (!targetEntry) throw new AgentHarnessError(\"invalid_argument\", `Entry ${targetId} not found`);\n\t\t\t\tconst { entries, commonAncestorId } = await collectEntriesForBranchSummary(\n\t\t\t\t\tthis.session,\n\t\t\t\t\toldLeafId,\n\t\t\t\t\ttargetId,\n\t\t\t\t);\n\t\t\t\tconst preparation = {\n\t\t\t\t\ttargetId,\n\t\t\t\t\toldLeafId,\n\t\t\t\t\tcommonAncestorId,\n\t\t\t\t\tentriesToSummarize: entries,\n\t\t\t\t\tuserWantsSummary: options?.summarize ?? false,\n\t\t\t\t\tcustomInstructions: options?.customInstructions,\n\t\t\t\t\treplaceInstructions: options?.replaceInstructions,\n\t\t\t\t\tlabel: options?.label,\n\t\t\t\t};\n\t\t\t\tconst signal = new AbortController().signal;\n\t\t\t\tconst hookResult = await this.emitHook({ type: \"session_before_tree\", preparation, signal });\n\t\t\t\tif (hookResult?.cancel) return { cancelled: true };\n\t\t\t\tlet summaryEntry: NavigateTreeResult[\"summaryEntry\"];\n\t\t\t\tlet summaryText: string | undefined = hookResult?.summary?.summary;\n\t\t\t\tlet summaryDetails: unknown = hookResult?.summary?.details;\n\t\t\t\tif (!summaryText && options?.summarize && entries.length > 0) {\n\t\t\t\t\tconst model = this.model;\n\t\t\t\t\tif (!model) throw new AgentHarnessError(\"invalid_state\", \"No model set for branch summary\");\n\t\t\t\t\tconst auth = await this.getApiKeyAndHeaders?.(model);\n\t\t\t\t\tif (!auth) throw new AgentHarnessError(\"auth\", \"No auth available for branch summary\");\n\t\t\t\t\tconst branchSummary = await runBranchSummary({\n\t\t\t\t\t\tentries,\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\tapiKey: auth.apiKey,\n\t\t\t\t\t\theaders: auth.headers,\n\t\t\t\t\t\tcustomInstructions: hookResult?.customInstructions ?? options?.customInstructions,\n\t\t\t\t\t\treplaceInstructions: hookResult?.replaceInstructions ?? options?.replaceInstructions,\n\t\t\t\t\t\tsummarizationRetry: this.streamOptions.summarizationRetry,\n\t\t\t\t\t\temit: (event) => this.emitOwn(event),\n\t\t\t\t\t});\n\t\t\t\t\tif (branchSummary.cancelled) return { cancelled: true };\n\t\t\t\t\tsummaryText = branchSummary.summary;\n\t\t\t\t\tsummaryDetails = branchSummary.details;\n\t\t\t\t}\n\t\t\t\tconst { newLeafId, editorText } = resolveNavigationTarget(targetEntry, targetId);\n\t\t\t\t// Single declared commit point of a tree navigation.\n\t\t\t\tthis.lifecycle.setStage(lease, \"committing\");\n\t\t\t\tconst summaryId = await this.session.moveTo(\n\t\t\t\t\tnewLeafId,\n\t\t\t\t\tsummaryText\n\t\t\t\t\t\t? { summary: summaryText, details: summaryDetails, fromHook: hookResult?.summary !== undefined }\n\t\t\t\t\t\t: undefined,\n\t\t\t\t);\n\t\t\t\tif (summaryId) {\n\t\t\t\t\tconst entry = await this.session.getEntry(summaryId);\n\t\t\t\t\tif (entry?.type === \"branch_summary\") summaryEntry = entry;\n\t\t\t\t}\n\t\t\t\tawait this.emitOwn({\n\t\t\t\t\ttype: \"session_tree\",\n\t\t\t\t\tnewLeafId: await this.session.getLeafId(),\n\t\t\t\t\toldLeafId,\n\t\t\t\t\tsummaryEntry,\n\t\t\t\t\tfromHook: hookResult?.summary !== undefined,\n\t\t\t\t});\n\t\t\t\treturn { cancelled: false, editorText, summaryEntry };\n\t\t\t},\n\t\t\tclassifyNavigateTreeOutcome,\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\tconst nextProvider = model.provider;\n\t\t\tconst nextModelId = model.id;\n\t\t\tawait this.persistConfigChange({ type: \"model_change\", provider: nextProvider, modelId: nextModelId });\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\tawait this.persistConfigChange({ type: \"thinking_level_change\", thinkingLevel: level });\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\tawait this.persistConfigChange({ type: \"active_tools_change\", activeToolNames: [...nextActiveToolNames] });\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\tconst nextActiveToolNames = [...toolNames];\n\t\t\tthis.validateToolNames(nextActiveToolNames);\n\t\t\tconst previousToolNames = [...this.tools.keys()];\n\t\t\tconst previousActiveToolNames = [...this.activeToolNames];\n\t\t\tawait this.persistConfigChange({ type: \"active_tools_change\", activeToolNames: [...nextActiveToolNames] });\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\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\t/**\n\t * Deliver the abort signal to the current operation without waiting for it to\n\t * settle. Safe from an operation's own callbacks: nothing here awaits\n\t * settlement, so it cannot form the cycle `abort()` has to refuse.\n\t */\n\trequestAbort(): AbortSignalDeliveryResult {\n\t\tassertAbortAllowed(this.lifecycle.getSnapshot());\n\t\treturn describeAbortDelivery(this.lifecycle.requestAbort());\n\t}\n\n\t/**\n\t * Queue work to run once the harness is idle and return its ref immediately.\n\t *\n\t * This is the callback-safe way to schedule follow-up work: awaiting\n\t * `waitForIdle()` or `abort()` from a callback of the operation being settled\n\t * deadlocks, because settlement awaits that callback. The ref reports the\n\t * command's outcome and cancels it while it is still queued.\n\t */\n\tasync runWhenIdle(command: DeferredHarnessCommand): Promise<CommandRef> {\n\t\treturn this.deferredCommands.enqueue(command);\n\t}\n\n\tasync abort(): Promise<AbortResult> {\n\t\t// Aborting awaits the captured operation's settlement, so a listener of that\n\t\t// same operation must never reach the wait below.\n\t\tthis.rejectCurrentOperationSelfWait(\"abort()\");\n\t\tassertAbortAllowed(this.lifecycle.getSnapshot());\n\t\t// Capture the current operation before delivering any signal: an operation\n\t\t// started later by a settlement listener is never this call's target.\n\t\tconst capture = this.lifecycle.requestAbort();\n\t\tconst clearedSteer = this.steerQueue.splice(0);\n\t\tconst clearedFollowUp = this.followUpQueue.splice(0);\n\t\tconst errors = await collectStepErrors([\n\t\t\t() => this.emitQueueUpdate(),\n\t\t\tasync () => {\n\t\t\t\tif (capture.target) await capture.target.settled;\n\t\t\t},\n\t\t\t() => this.emitOwn({ type: \"abort\", clearedSteer, clearedFollowUp }),\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\tthis.rejectCurrentOperationSelfWait(\"waitForIdle()\");\n\t\t// Delegates to the lifecycle: resolves once no operation is active or settling.\n\t\tawait this.lifecycle.waitForIdle();\n\t}\n\n\tsubscribe(\n\t\tlistener: (event: AgentHarnessEvent<TSkill, TPromptTemplate>, signal?: AbortSignal) => Promise<void> | void,\n\t): () => void {\n\t\treturn this.subscribers.subscribe(listener);\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"]}