omk-agent-core 0.98.1 → 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.
- package/CHANGELOG.md +644 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +3 -5
- package/dist/agent.js.map +1 -1
- package/dist/effects/effect-journal.d.ts +43 -0
- package/dist/effects/effect-journal.d.ts.map +1 -0
- package/dist/effects/effect-journal.js +186 -0
- package/dist/effects/effect-journal.js.map +1 -0
- package/dist/effects/effect-recovery.d.ts +70 -0
- package/dist/effects/effect-recovery.d.ts.map +1 -0
- package/dist/effects/effect-recovery.js +120 -0
- package/dist/effects/effect-recovery.js.map +1 -0
- package/dist/effects/effect-transitions.d.ts +34 -0
- package/dist/effects/effect-transitions.d.ts.map +1 -0
- package/dist/effects/effect-transitions.js +148 -0
- package/dist/effects/effect-transitions.js.map +1 -0
- package/dist/effects/effect-types.d.ts +135 -0
- package/dist/effects/effect-types.d.ts.map +1 -0
- package/dist/effects/effect-types.js +32 -0
- package/dist/effects/effect-types.js.map +1 -0
- package/dist/harness/abort-delivery.d.ts +26 -0
- package/dist/harness/abort-delivery.d.ts.map +1 -0
- package/dist/harness/abort-delivery.js +36 -0
- package/dist/harness/abort-delivery.js.map +1 -0
- package/dist/harness/agent-harness.d.ts +38 -6
- package/dist/harness/agent-harness.d.ts.map +1 -1
- package/dist/harness/agent-harness.js +327 -325
- package/dist/harness/agent-harness.js.map +1 -1
- package/dist/harness/canonical-digest.d.ts +32 -0
- package/dist/harness/canonical-digest.d.ts.map +1 -0
- package/dist/harness/canonical-digest.js +164 -0
- package/dist/harness/canonical-digest.js.map +1 -0
- package/dist/harness/compaction/operation.d.ts +7 -0
- package/dist/harness/compaction/operation.d.ts.map +1 -1
- package/dist/harness/compaction/operation.js.map +1 -1
- package/dist/harness/deferred-commands.d.ts +53 -0
- package/dist/harness/deferred-commands.d.ts.map +1 -0
- package/dist/harness/deferred-commands.js +96 -0
- package/dist/harness/deferred-commands.js.map +1 -0
- package/dist/harness/harness-session.d.ts +5 -58
- package/dist/harness/harness-session.d.ts.map +1 -1
- package/dist/harness/harness-session.js +15 -18
- package/dist/harness/harness-session.js.map +1 -1
- package/dist/harness/operation-lifecycle-controller.d.ts +68 -0
- package/dist/harness/operation-lifecycle-controller.d.ts.map +1 -0
- package/dist/harness/operation-lifecycle-controller.js +199 -0
- package/dist/harness/operation-lifecycle-controller.js.map +1 -0
- package/dist/harness/operation-lifecycle-reducer.d.ts +19 -0
- package/dist/harness/operation-lifecycle-reducer.d.ts.map +1 -0
- package/dist/harness/operation-lifecycle-reducer.js +201 -0
- package/dist/harness/operation-lifecycle-reducer.js.map +1 -0
- package/dist/harness/operation-lifecycle-types.d.ts +130 -0
- package/dist/harness/operation-lifecycle-types.d.ts.map +1 -0
- package/dist/harness/operation-lifecycle-types.js +34 -0
- package/dist/harness/operation-lifecycle-types.js.map +1 -0
- package/dist/harness/operation-outcome.d.ts +79 -0
- package/dist/harness/operation-outcome.d.ts.map +1 -0
- package/dist/harness/operation-outcome.js +167 -0
- package/dist/harness/operation-outcome.js.map +1 -0
- package/dist/harness/operation-trace-divergence.d.ts +60 -0
- package/dist/harness/operation-trace-divergence.d.ts.map +1 -0
- package/dist/harness/operation-trace-divergence.js +199 -0
- package/dist/harness/operation-trace-divergence.js.map +1 -0
- package/dist/harness/operation-trace.d.ts +134 -0
- package/dist/harness/operation-trace.d.ts.map +1 -0
- package/dist/harness/operation-trace.js +161 -0
- package/dist/harness/operation-trace.js.map +1 -0
- package/dist/harness/session-write-coordinator.d.ts +76 -0
- package/dist/harness/session-write-coordinator.d.ts.map +1 -0
- package/dist/harness/session-write-coordinator.js +126 -0
- package/dist/harness/session-write-coordinator.js.map +1 -0
- package/dist/harness/subscriber-fanout.d.ts +50 -0
- package/dist/harness/subscriber-fanout.d.ts.map +1 -0
- package/dist/harness/subscriber-fanout.js +92 -0
- package/dist/harness/subscriber-fanout.js.map +1 -0
- package/dist/harness/tree-navigation.d.ts +45 -0
- package/dist/harness/tree-navigation.d.ts.map +1 -0
- package/dist/harness/tree-navigation.js +59 -0
- package/dist/harness/tree-navigation.js.map +1 -0
- package/dist/harness/types.d.ts +34 -3
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/listener-delivery.d.ts +22 -0
- package/dist/listener-delivery.d.ts.map +1 -0
- package/dist/listener-delivery.js +36 -0
- package/dist/listener-delivery.js.map +1 -0
- package/package.json +4 -3
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { ImageContent, TextContent } from "omk-ai";
|
|
2
|
+
export type QueuedSessionWrite<TMessage> = {
|
|
3
|
+
readonly type: "message";
|
|
4
|
+
readonly message: TMessage;
|
|
5
|
+
} | {
|
|
6
|
+
readonly type: "thinking_level_change";
|
|
7
|
+
readonly thinkingLevel: string;
|
|
8
|
+
} | {
|
|
9
|
+
readonly type: "model_change";
|
|
10
|
+
readonly provider: string;
|
|
11
|
+
readonly modelId: string;
|
|
12
|
+
} | {
|
|
13
|
+
readonly type: "active_tools_change";
|
|
14
|
+
readonly activeToolNames: string[];
|
|
15
|
+
} | {
|
|
16
|
+
readonly type: "custom";
|
|
17
|
+
readonly customType: string;
|
|
18
|
+
readonly data?: unknown;
|
|
19
|
+
} | {
|
|
20
|
+
readonly type: "custom_message";
|
|
21
|
+
readonly customType: string;
|
|
22
|
+
readonly content: string | (TextContent | ImageContent)[];
|
|
23
|
+
readonly details?: unknown;
|
|
24
|
+
readonly display: boolean;
|
|
25
|
+
} | {
|
|
26
|
+
readonly type: "label";
|
|
27
|
+
readonly targetId: string;
|
|
28
|
+
readonly label: string | undefined;
|
|
29
|
+
} | {
|
|
30
|
+
readonly type: "session_info";
|
|
31
|
+
readonly name?: string;
|
|
32
|
+
} | {
|
|
33
|
+
readonly type: "leaf";
|
|
34
|
+
readonly targetId: string | null;
|
|
35
|
+
};
|
|
36
|
+
interface SessionWritePort<TMessage> {
|
|
37
|
+
getStorage(): {
|
|
38
|
+
setLeafId(targetId: string | null): Promise<void>;
|
|
39
|
+
};
|
|
40
|
+
appendMessage(message: TMessage): Promise<unknown>;
|
|
41
|
+
appendThinkingLevelChange(thinkingLevel: string): Promise<unknown>;
|
|
42
|
+
appendModelChange(provider: string, modelId: string): Promise<unknown>;
|
|
43
|
+
appendActiveToolsChange(activeToolNames: string[]): Promise<unknown>;
|
|
44
|
+
appendCustomEntry(customType: string, data?: unknown): Promise<unknown>;
|
|
45
|
+
appendCustomMessageEntry(customType: string, content: string | (TextContent | ImageContent)[], display: boolean, details?: unknown): Promise<unknown>;
|
|
46
|
+
appendLabel(targetId: string, label: string | undefined): Promise<unknown>;
|
|
47
|
+
appendSessionName(name: string): Promise<unknown>;
|
|
48
|
+
moveTo(targetId: string | null): Promise<unknown>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Owns ordered pending writes and serializes coordinator-routed persistence.
|
|
52
|
+
*
|
|
53
|
+
* Persistence follows *acceptance* order, not the queue contents observed when
|
|
54
|
+
* a boundary finally runs. Each boundary captures a watermark at invocation
|
|
55
|
+
* time and drains only writes accepted at or before it, so a write enqueued
|
|
56
|
+
* after an idle boundary was reserved can never overtake it.
|
|
57
|
+
*/
|
|
58
|
+
export declare class SessionWriteCoordinator<TMessage> {
|
|
59
|
+
private readonly pendingWrites;
|
|
60
|
+
private nextSequence;
|
|
61
|
+
private operationTail?;
|
|
62
|
+
private invokingOperation;
|
|
63
|
+
private readonly session;
|
|
64
|
+
constructor(session: SessionWritePort<TMessage>);
|
|
65
|
+
enqueue(write: QueuedSessionWrite<TMessage>): void;
|
|
66
|
+
hasPending(): boolean;
|
|
67
|
+
snapshot(): readonly QueuedSessionWrite<TMessage>[];
|
|
68
|
+
flush(): Promise<void>;
|
|
69
|
+
persistAfterPending(write: QueuedSessionWrite<TMessage>): Promise<void>;
|
|
70
|
+
private flushPendingThrough;
|
|
71
|
+
private persistIdleWrite;
|
|
72
|
+
private persist;
|
|
73
|
+
private serialize;
|
|
74
|
+
}
|
|
75
|
+
export {};
|
|
76
|
+
//# sourceMappingURL=session-write-coordinator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-write-coordinator.d.ts","sourceRoot":"","sources":["../../src/harness/session-write-coordinator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAIxD,MAAM,MAAM,kBAAkB,CAAC,QAAQ,IACpC;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAA;CAAE,GACxD;IAAE,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAC;IAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;CAAE,GAC1E;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACtF;IAAE,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IAAC,QAAQ,CAAC,eAAe,EAAE,MAAM,EAAE,CAAA;CAAE,GAC5E;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GACjF;IACA,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,CAAC,WAAW,GAAG,YAAY,CAAC,EAAE,CAAC;IAC1D,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CACzB,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACzF;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACzD;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAE/D,UAAU,gBAAgB,CAAC,QAAQ;IAClC,UAAU,IAAI;QAAE,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAC;IACpE,aAAa,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,yBAAyB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnE,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvE,uBAAuB,CAAC,eAAe,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrE,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACxE,wBAAwB,CACvB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GAAG,CAAC,WAAW,GAAG,YAAY,CAAC,EAAE,EAChD,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,OAAO,GACf,OAAO,CAAC,OAAO,CAAC,CAAC;IACpB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3E,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClD,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAClD;AAQD;;;;;;;GAOG;AACH,qBAAa,uBAAuB,CAAC,QAAQ;IAC5C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAyC;IACvE,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,aAAa,CAAC,CAAgB;IACtC,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IAErD,YAAY,OAAO,EAAE,gBAAgB,CAAC,QAAQ,CAAC,EAE9C;IAED,OAAO,CAAC,KAAK,EAAE,kBAAkB,CAAC,QAAQ,CAAC,GAAG,IAAI,CAEjD;IAED,UAAU,IAAI,OAAO,CAEpB;IAED,QAAQ,IAAI,SAAS,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAGlD;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAG3B;IAEK,mBAAmB,CAAC,KAAK,EAAE,kBAAkB,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ5E;YAGa,mBAAmB;YAUnB,gBAAgB;YAQhB,OAAO;IA4CrB,OAAO,CAAC,SAAS;CA2BjB","sourcesContent":["import type { ImageContent, TextContent } from \"omk-ai\";\nimport { createImmutableSnapshot } from \"../plain-data.ts\";\nimport { AgentHarnessError } from \"./errors.ts\";\n\nexport type QueuedSessionWrite<TMessage> =\n\t| { readonly type: \"message\"; readonly message: TMessage }\n\t| { readonly type: \"thinking_level_change\"; readonly thinkingLevel: string }\n\t| { readonly type: \"model_change\"; readonly provider: string; readonly modelId: string }\n\t| { readonly type: \"active_tools_change\"; readonly activeToolNames: string[] }\n\t| { readonly type: \"custom\"; readonly customType: string; readonly data?: unknown }\n\t| {\n\t\t\treadonly type: \"custom_message\";\n\t\t\treadonly customType: string;\n\t\t\treadonly content: string | (TextContent | ImageContent)[];\n\t\t\treadonly details?: unknown;\n\t\t\treadonly display: boolean;\n\t }\n\t| { readonly type: \"label\"; readonly targetId: string; readonly label: string | undefined }\n\t| { readonly type: \"session_info\"; readonly name?: string }\n\t| { readonly type: \"leaf\"; readonly targetId: string | null };\n\ninterface SessionWritePort<TMessage> {\n\tgetStorage(): { setLeafId(targetId: string | null): Promise<void> };\n\tappendMessage(message: TMessage): Promise<unknown>;\n\tappendThinkingLevelChange(thinkingLevel: string): Promise<unknown>;\n\tappendModelChange(provider: string, modelId: string): Promise<unknown>;\n\tappendActiveToolsChange(activeToolNames: string[]): Promise<unknown>;\n\tappendCustomEntry(customType: string, data?: unknown): Promise<unknown>;\n\tappendCustomMessageEntry(\n\t\tcustomType: string,\n\t\tcontent: string | (TextContent | ImageContent)[],\n\t\tdisplay: boolean,\n\t\tdetails?: unknown,\n\t): Promise<unknown>;\n\tappendLabel(targetId: string, label: string | undefined): Promise<unknown>;\n\tappendSessionName(name: string): Promise<unknown>;\n\tmoveTo(targetId: string | null): Promise<unknown>;\n}\n\n/** A queued write tagged with the monotonic order in which it was accepted. */\ninterface SequencedSessionWrite<TMessage> {\n\treadonly sequence: number;\n\treadonly write: QueuedSessionWrite<TMessage>;\n}\n\n/**\n * Owns ordered pending writes and serializes coordinator-routed persistence.\n *\n * Persistence follows *acceptance* order, not the queue contents observed when\n * a boundary finally runs. Each boundary captures a watermark at invocation\n * time and drains only writes accepted at or before it, so a write enqueued\n * after an idle boundary was reserved can never overtake it.\n */\nexport class SessionWriteCoordinator<TMessage> {\n\tprivate readonly pendingWrites: SequencedSessionWrite<TMessage>[] = [];\n\tprivate nextSequence = 1;\n\tprivate operationTail?: Promise<void>;\n\tprivate invokingOperation = false;\n\tprivate readonly session: SessionWritePort<TMessage>;\n\n\tconstructor(session: SessionWritePort<TMessage>) {\n\t\tthis.session = session;\n\t}\n\n\tenqueue(write: QueuedSessionWrite<TMessage>): void {\n\t\tthis.pendingWrites.push({ sequence: this.nextSequence++, write: createImmutableSnapshot(write) });\n\t}\n\n\thasPending(): boolean {\n\t\treturn this.pendingWrites.length > 0;\n\t}\n\n\tsnapshot(): readonly QueuedSessionWrite<TMessage>[] {\n\t\t// Each queued write was frozen on acceptance, so exposing them needs no clone.\n\t\treturn Object.freeze(this.pendingWrites.map((entry) => entry.write));\n\t}\n\n\tasync flush(): Promise<void> {\n\t\tconst acceptedThrough = this.nextSequence - 1;\n\t\tawait this.serialize(async () => await this.flushPendingThrough(acceptedThrough));\n\t}\n\n\tasync persistAfterPending(write: QueuedSessionWrite<TMessage>): Promise<void> {\n\t\tconst snapshot = createImmutableSnapshot(write);\n\t\t// Reserve the boundary now: only writes already accepted precede this one.\n\t\tconst acceptedThrough = this.nextSequence - 1;\n\t\tawait this.serialize(async () => {\n\t\t\tawait this.flushPendingThrough(acceptedThrough);\n\t\t\tawait this.persistIdleWrite(snapshot);\n\t\t});\n\t}\n\n\t/** Drains queued writes up to `acceptedThrough`; a failed head stays at the head. */\n\tprivate async flushPendingThrough(acceptedThrough: number): Promise<void> {\n\t\twhile (this.pendingWrites.length > 0) {\n\t\t\tconst head = this.pendingWrites[0];\n\t\t\tif (head === undefined || head.sequence > acceptedThrough) return;\n\t\t\tawait this.persist(head.write);\n\t\t\tthis.pendingWrites.shift();\n\t\t}\n\t}\n\n\t/** Idle leaf moves keep the summarizing `Session.moveTo()` path. */\n\tprivate async persistIdleWrite(write: QueuedSessionWrite<TMessage>): Promise<void> {\n\t\tif (write.type === \"leaf\") {\n\t\t\tawait this.session.moveTo(write.targetId);\n\t\t\treturn;\n\t\t}\n\t\tawait this.persist(write);\n\t}\n\n\tprivate async persist(write: QueuedSessionWrite<TMessage>): Promise<void> {\n\t\tswitch (write.type) {\n\t\t\tcase \"message\":\n\t\t\t\tawait this.session.appendMessage(write.message);\n\t\t\t\treturn;\n\t\t\tcase \"thinking_level_change\":\n\t\t\t\tawait this.session.appendThinkingLevelChange(write.thinkingLevel);\n\t\t\t\treturn;\n\t\t\tcase \"model_change\":\n\t\t\t\tawait this.session.appendModelChange(write.provider, write.modelId);\n\t\t\t\treturn;\n\t\t\tcase \"active_tools_change\":\n\t\t\t\tawait this.session.appendActiveToolsChange([...write.activeToolNames]);\n\t\t\t\treturn;\n\t\t\tcase \"custom\":\n\t\t\t\tawait this.session.appendCustomEntry(write.customType, write.data);\n\t\t\t\treturn;\n\t\t\tcase \"custom_message\":\n\t\t\t\tawait this.session.appendCustomMessageEntry(\n\t\t\t\t\twrite.customType,\n\t\t\t\t\ttypeof write.content === \"string\" ? write.content : [...write.content],\n\t\t\t\t\twrite.display,\n\t\t\t\t\twrite.details,\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\tcase \"label\":\n\t\t\t\tawait this.session.appendLabel(write.targetId, write.label);\n\t\t\t\treturn;\n\t\t\tcase \"session_info\":\n\t\t\t\tawait this.session.appendSessionName(write.name ?? \"\");\n\t\t\t\treturn;\n\t\t\tcase \"leaf\":\n\t\t\t\tawait this.session.getStorage().setLeafId(write.targetId);\n\t\t\t\treturn;\n\t\t\tdefault: {\n\t\t\t\tconst unsupportedWrite: never = write;\n\t\t\t\tthrow new AgentHarnessError(\n\t\t\t\t\t\"invalid_state\",\n\t\t\t\t\t`Unsupported pending session write: ${String(unsupportedWrite)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate serialize(operation: () => Promise<void>): Promise<void> {\n\t\tif (this.invokingOperation) {\n\t\t\tthrow new AgentHarnessError(\"invalid_state\", \"Session persistence cannot synchronously reenter itself\");\n\t\t}\n\t\tconst previous = this.operationTail;\n\t\tlet releaseTail = (): void => undefined;\n\t\tconst tail = new Promise<void>((resolve) => {\n\t\t\treleaseTail = resolve;\n\t\t});\n\t\tthis.operationTail = tail;\n\t\tconst invoke = (): Promise<void> => {\n\t\t\tthis.invokingOperation = true;\n\t\t\ttry {\n\t\t\t\treturn operation();\n\t\t\t} finally {\n\t\t\t\tthis.invokingOperation = false;\n\t\t\t}\n\t\t};\n\t\tconst next = previous ? previous.then(invoke) : invoke();\n\t\tconst release = (): void => {\n\t\t\treleaseTail();\n\t\t\tif (this.operationTail === tail) this.operationTail = undefined;\n\t\t};\n\t\t// Keep the serialization tail usable after a caller observes a failed write.\n\t\tvoid next.then(release, release);\n\t\treturn next;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { createImmutableSnapshot } from "../plain-data.js";
|
|
2
|
+
import { AgentHarnessError } from "./errors.js";
|
|
3
|
+
/**
|
|
4
|
+
* Owns ordered pending writes and serializes coordinator-routed persistence.
|
|
5
|
+
*
|
|
6
|
+
* Persistence follows *acceptance* order, not the queue contents observed when
|
|
7
|
+
* a boundary finally runs. Each boundary captures a watermark at invocation
|
|
8
|
+
* time and drains only writes accepted at or before it, so a write enqueued
|
|
9
|
+
* after an idle boundary was reserved can never overtake it.
|
|
10
|
+
*/
|
|
11
|
+
export class SessionWriteCoordinator {
|
|
12
|
+
pendingWrites = [];
|
|
13
|
+
nextSequence = 1;
|
|
14
|
+
operationTail;
|
|
15
|
+
invokingOperation = false;
|
|
16
|
+
session;
|
|
17
|
+
constructor(session) {
|
|
18
|
+
this.session = session;
|
|
19
|
+
}
|
|
20
|
+
enqueue(write) {
|
|
21
|
+
this.pendingWrites.push({ sequence: this.nextSequence++, write: createImmutableSnapshot(write) });
|
|
22
|
+
}
|
|
23
|
+
hasPending() {
|
|
24
|
+
return this.pendingWrites.length > 0;
|
|
25
|
+
}
|
|
26
|
+
snapshot() {
|
|
27
|
+
// Each queued write was frozen on acceptance, so exposing them needs no clone.
|
|
28
|
+
return Object.freeze(this.pendingWrites.map((entry) => entry.write));
|
|
29
|
+
}
|
|
30
|
+
async flush() {
|
|
31
|
+
const acceptedThrough = this.nextSequence - 1;
|
|
32
|
+
await this.serialize(async () => await this.flushPendingThrough(acceptedThrough));
|
|
33
|
+
}
|
|
34
|
+
async persistAfterPending(write) {
|
|
35
|
+
const snapshot = createImmutableSnapshot(write);
|
|
36
|
+
// Reserve the boundary now: only writes already accepted precede this one.
|
|
37
|
+
const acceptedThrough = this.nextSequence - 1;
|
|
38
|
+
await this.serialize(async () => {
|
|
39
|
+
await this.flushPendingThrough(acceptedThrough);
|
|
40
|
+
await this.persistIdleWrite(snapshot);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/** Drains queued writes up to `acceptedThrough`; a failed head stays at the head. */
|
|
44
|
+
async flushPendingThrough(acceptedThrough) {
|
|
45
|
+
while (this.pendingWrites.length > 0) {
|
|
46
|
+
const head = this.pendingWrites[0];
|
|
47
|
+
if (head === undefined || head.sequence > acceptedThrough)
|
|
48
|
+
return;
|
|
49
|
+
await this.persist(head.write);
|
|
50
|
+
this.pendingWrites.shift();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Idle leaf moves keep the summarizing `Session.moveTo()` path. */
|
|
54
|
+
async persistIdleWrite(write) {
|
|
55
|
+
if (write.type === "leaf") {
|
|
56
|
+
await this.session.moveTo(write.targetId);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
await this.persist(write);
|
|
60
|
+
}
|
|
61
|
+
async persist(write) {
|
|
62
|
+
switch (write.type) {
|
|
63
|
+
case "message":
|
|
64
|
+
await this.session.appendMessage(write.message);
|
|
65
|
+
return;
|
|
66
|
+
case "thinking_level_change":
|
|
67
|
+
await this.session.appendThinkingLevelChange(write.thinkingLevel);
|
|
68
|
+
return;
|
|
69
|
+
case "model_change":
|
|
70
|
+
await this.session.appendModelChange(write.provider, write.modelId);
|
|
71
|
+
return;
|
|
72
|
+
case "active_tools_change":
|
|
73
|
+
await this.session.appendActiveToolsChange([...write.activeToolNames]);
|
|
74
|
+
return;
|
|
75
|
+
case "custom":
|
|
76
|
+
await this.session.appendCustomEntry(write.customType, write.data);
|
|
77
|
+
return;
|
|
78
|
+
case "custom_message":
|
|
79
|
+
await this.session.appendCustomMessageEntry(write.customType, typeof write.content === "string" ? write.content : [...write.content], write.display, write.details);
|
|
80
|
+
return;
|
|
81
|
+
case "label":
|
|
82
|
+
await this.session.appendLabel(write.targetId, write.label);
|
|
83
|
+
return;
|
|
84
|
+
case "session_info":
|
|
85
|
+
await this.session.appendSessionName(write.name ?? "");
|
|
86
|
+
return;
|
|
87
|
+
case "leaf":
|
|
88
|
+
await this.session.getStorage().setLeafId(write.targetId);
|
|
89
|
+
return;
|
|
90
|
+
default: {
|
|
91
|
+
const unsupportedWrite = write;
|
|
92
|
+
throw new AgentHarnessError("invalid_state", `Unsupported pending session write: ${String(unsupportedWrite)}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
serialize(operation) {
|
|
97
|
+
if (this.invokingOperation) {
|
|
98
|
+
throw new AgentHarnessError("invalid_state", "Session persistence cannot synchronously reenter itself");
|
|
99
|
+
}
|
|
100
|
+
const previous = this.operationTail;
|
|
101
|
+
let releaseTail = () => undefined;
|
|
102
|
+
const tail = new Promise((resolve) => {
|
|
103
|
+
releaseTail = resolve;
|
|
104
|
+
});
|
|
105
|
+
this.operationTail = tail;
|
|
106
|
+
const invoke = () => {
|
|
107
|
+
this.invokingOperation = true;
|
|
108
|
+
try {
|
|
109
|
+
return operation();
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
this.invokingOperation = false;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const next = previous ? previous.then(invoke) : invoke();
|
|
116
|
+
const release = () => {
|
|
117
|
+
releaseTail();
|
|
118
|
+
if (this.operationTail === tail)
|
|
119
|
+
this.operationTail = undefined;
|
|
120
|
+
};
|
|
121
|
+
// Keep the serialization tail usable after a caller observes a failed write.
|
|
122
|
+
void next.then(release, release);
|
|
123
|
+
return next;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=session-write-coordinator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-write-coordinator.js","sourceRoot":"","sources":["../../src/harness/session-write-coordinator.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ImageContent, TextContent } from \"omk-ai\";\nimport { createImmutableSnapshot } from \"../plain-data.ts\";\nimport { AgentHarnessError } from \"./errors.ts\";\n\nexport type QueuedSessionWrite<TMessage> =\n\t| { readonly type: \"message\"; readonly message: TMessage }\n\t| { readonly type: \"thinking_level_change\"; readonly thinkingLevel: string }\n\t| { readonly type: \"model_change\"; readonly provider: string; readonly modelId: string }\n\t| { readonly type: \"active_tools_change\"; readonly activeToolNames: string[] }\n\t| { readonly type: \"custom\"; readonly customType: string; readonly data?: unknown }\n\t| {\n\t\t\treadonly type: \"custom_message\";\n\t\t\treadonly customType: string;\n\t\t\treadonly content: string | (TextContent | ImageContent)[];\n\t\t\treadonly details?: unknown;\n\t\t\treadonly display: boolean;\n\t }\n\t| { readonly type: \"label\"; readonly targetId: string; readonly label: string | undefined }\n\t| { readonly type: \"session_info\"; readonly name?: string }\n\t| { readonly type: \"leaf\"; readonly targetId: string | null };\n\ninterface SessionWritePort<TMessage> {\n\tgetStorage(): { setLeafId(targetId: string | null): Promise<void> };\n\tappendMessage(message: TMessage): Promise<unknown>;\n\tappendThinkingLevelChange(thinkingLevel: string): Promise<unknown>;\n\tappendModelChange(provider: string, modelId: string): Promise<unknown>;\n\tappendActiveToolsChange(activeToolNames: string[]): Promise<unknown>;\n\tappendCustomEntry(customType: string, data?: unknown): Promise<unknown>;\n\tappendCustomMessageEntry(\n\t\tcustomType: string,\n\t\tcontent: string | (TextContent | ImageContent)[],\n\t\tdisplay: boolean,\n\t\tdetails?: unknown,\n\t): Promise<unknown>;\n\tappendLabel(targetId: string, label: string | undefined): Promise<unknown>;\n\tappendSessionName(name: string): Promise<unknown>;\n\tmoveTo(targetId: string | null): Promise<unknown>;\n}\n\n/** A queued write tagged with the monotonic order in which it was accepted. */\ninterface SequencedSessionWrite<TMessage> {\n\treadonly sequence: number;\n\treadonly write: QueuedSessionWrite<TMessage>;\n}\n\n/**\n * Owns ordered pending writes and serializes coordinator-routed persistence.\n *\n * Persistence follows *acceptance* order, not the queue contents observed when\n * a boundary finally runs. Each boundary captures a watermark at invocation\n * time and drains only writes accepted at or before it, so a write enqueued\n * after an idle boundary was reserved can never overtake it.\n */\nexport class SessionWriteCoordinator<TMessage> {\n\tprivate readonly pendingWrites: SequencedSessionWrite<TMessage>[] = [];\n\tprivate nextSequence = 1;\n\tprivate operationTail?: Promise<void>;\n\tprivate invokingOperation = false;\n\tprivate readonly session: SessionWritePort<TMessage>;\n\n\tconstructor(session: SessionWritePort<TMessage>) {\n\t\tthis.session = session;\n\t}\n\n\tenqueue(write: QueuedSessionWrite<TMessage>): void {\n\t\tthis.pendingWrites.push({ sequence: this.nextSequence++, write: createImmutableSnapshot(write) });\n\t}\n\n\thasPending(): boolean {\n\t\treturn this.pendingWrites.length > 0;\n\t}\n\n\tsnapshot(): readonly QueuedSessionWrite<TMessage>[] {\n\t\t// Each queued write was frozen on acceptance, so exposing them needs no clone.\n\t\treturn Object.freeze(this.pendingWrites.map((entry) => entry.write));\n\t}\n\n\tasync flush(): Promise<void> {\n\t\tconst acceptedThrough = this.nextSequence - 1;\n\t\tawait this.serialize(async () => await this.flushPendingThrough(acceptedThrough));\n\t}\n\n\tasync persistAfterPending(write: QueuedSessionWrite<TMessage>): Promise<void> {\n\t\tconst snapshot = createImmutableSnapshot(write);\n\t\t// Reserve the boundary now: only writes already accepted precede this one.\n\t\tconst acceptedThrough = this.nextSequence - 1;\n\t\tawait this.serialize(async () => {\n\t\t\tawait this.flushPendingThrough(acceptedThrough);\n\t\t\tawait this.persistIdleWrite(snapshot);\n\t\t});\n\t}\n\n\t/** Drains queued writes up to `acceptedThrough`; a failed head stays at the head. */\n\tprivate async flushPendingThrough(acceptedThrough: number): Promise<void> {\n\t\twhile (this.pendingWrites.length > 0) {\n\t\t\tconst head = this.pendingWrites[0];\n\t\t\tif (head === undefined || head.sequence > acceptedThrough) return;\n\t\t\tawait this.persist(head.write);\n\t\t\tthis.pendingWrites.shift();\n\t\t}\n\t}\n\n\t/** Idle leaf moves keep the summarizing `Session.moveTo()` path. */\n\tprivate async persistIdleWrite(write: QueuedSessionWrite<TMessage>): Promise<void> {\n\t\tif (write.type === \"leaf\") {\n\t\t\tawait this.session.moveTo(write.targetId);\n\t\t\treturn;\n\t\t}\n\t\tawait this.persist(write);\n\t}\n\n\tprivate async persist(write: QueuedSessionWrite<TMessage>): Promise<void> {\n\t\tswitch (write.type) {\n\t\t\tcase \"message\":\n\t\t\t\tawait this.session.appendMessage(write.message);\n\t\t\t\treturn;\n\t\t\tcase \"thinking_level_change\":\n\t\t\t\tawait this.session.appendThinkingLevelChange(write.thinkingLevel);\n\t\t\t\treturn;\n\t\t\tcase \"model_change\":\n\t\t\t\tawait this.session.appendModelChange(write.provider, write.modelId);\n\t\t\t\treturn;\n\t\t\tcase \"active_tools_change\":\n\t\t\t\tawait this.session.appendActiveToolsChange([...write.activeToolNames]);\n\t\t\t\treturn;\n\t\t\tcase \"custom\":\n\t\t\t\tawait this.session.appendCustomEntry(write.customType, write.data);\n\t\t\t\treturn;\n\t\t\tcase \"custom_message\":\n\t\t\t\tawait this.session.appendCustomMessageEntry(\n\t\t\t\t\twrite.customType,\n\t\t\t\t\ttypeof write.content === \"string\" ? write.content : [...write.content],\n\t\t\t\t\twrite.display,\n\t\t\t\t\twrite.details,\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\tcase \"label\":\n\t\t\t\tawait this.session.appendLabel(write.targetId, write.label);\n\t\t\t\treturn;\n\t\t\tcase \"session_info\":\n\t\t\t\tawait this.session.appendSessionName(write.name ?? \"\");\n\t\t\t\treturn;\n\t\t\tcase \"leaf\":\n\t\t\t\tawait this.session.getStorage().setLeafId(write.targetId);\n\t\t\t\treturn;\n\t\t\tdefault: {\n\t\t\t\tconst unsupportedWrite: never = write;\n\t\t\t\tthrow new AgentHarnessError(\n\t\t\t\t\t\"invalid_state\",\n\t\t\t\t\t`Unsupported pending session write: ${String(unsupportedWrite)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate serialize(operation: () => Promise<void>): Promise<void> {\n\t\tif (this.invokingOperation) {\n\t\t\tthrow new AgentHarnessError(\"invalid_state\", \"Session persistence cannot synchronously reenter itself\");\n\t\t}\n\t\tconst previous = this.operationTail;\n\t\tlet releaseTail = (): void => undefined;\n\t\tconst tail = new Promise<void>((resolve) => {\n\t\t\treleaseTail = resolve;\n\t\t});\n\t\tthis.operationTail = tail;\n\t\tconst invoke = (): Promise<void> => {\n\t\t\tthis.invokingOperation = true;\n\t\t\ttry {\n\t\t\t\treturn operation();\n\t\t\t} finally {\n\t\t\t\tthis.invokingOperation = false;\n\t\t\t}\n\t\t};\n\t\tconst next = previous ? previous.then(invoke) : invoke();\n\t\tconst release = (): void => {\n\t\t\treleaseTail();\n\t\t\tif (this.operationTail === tail) this.operationTail = undefined;\n\t\t};\n\t\t// Keep the serialization tail usable after a caller observes a failed write.\n\t\tvoid next.then(release, release);\n\t\treturn next;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subscriber fan-out with a self-wait barrier, extracted from `AgentHarness`.
|
|
3
|
+
*
|
|
4
|
+
* A listener that awaits the end of the very operation whose emission it is
|
|
5
|
+
* blocking forms a cycle. The call that closes that cycle (`waitForIdle()`,
|
|
6
|
+
* `abort()`) is always made during the listener's *synchronous* prologue — an
|
|
7
|
+
* async function body runs synchronously up to its first `await` — so marking
|
|
8
|
+
* just that window identifies callback-originated waits exactly, without
|
|
9
|
+
* falsely rejecting an unrelated concurrent caller that runs later while the
|
|
10
|
+
* listener's promise is merely pending.
|
|
11
|
+
*
|
|
12
|
+
* Delivery is observational: every subscriber runs even when an earlier one
|
|
13
|
+
* fails, and the failures are raised together afterwards.
|
|
14
|
+
*
|
|
15
|
+
* This module imports no harness or session types so it stays a leaf for the
|
|
16
|
+
* import-cycle ratchet; the harness passes the current operation id in.
|
|
17
|
+
*/
|
|
18
|
+
export type SubscriberListener<TEvent> = (event: TEvent, signal?: AbortSignal) => Promise<void> | void;
|
|
19
|
+
export declare class SubscriberFanout<TEvent extends {
|
|
20
|
+
readonly type: string;
|
|
21
|
+
}> {
|
|
22
|
+
private readonly listeners;
|
|
23
|
+
/** Non-zero only while a subscriber's synchronous prologue is on the stack. */
|
|
24
|
+
private syncFrameDepth;
|
|
25
|
+
private operationId;
|
|
26
|
+
private eventType;
|
|
27
|
+
subscribe(listener: SubscriberListener<TEvent>): () => void;
|
|
28
|
+
/**
|
|
29
|
+
* Deliver `event` to every subscriber in registration order, then fail the
|
|
30
|
+
* emission once the fanout has completed.
|
|
31
|
+
*
|
|
32
|
+
* Observational subscribers must not starve each other: one throwing telemetry
|
|
33
|
+
* or audit listener cannot hide `settled` / `attempt_finished` from the
|
|
34
|
+
* listeners registered after it. Failures are collected and raised after the
|
|
35
|
+
* last listener ran — a single failure keeps its own classification, several
|
|
36
|
+
* become one `AggregateError` classified by the first. Policy and mutation
|
|
37
|
+
* hooks live on `AgentHarness.on()` and stay fail-fast.
|
|
38
|
+
*/
|
|
39
|
+
emit(event: TEvent, currentOperationId: string | undefined, signal?: AbortSignal): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Fail closed when an awaited listener tries to wait on its own operation.
|
|
42
|
+
* Rejecting immediately turns a permanent deadlock into a classified error.
|
|
43
|
+
*
|
|
44
|
+
* Scope: this catches a wait issued from the listener's synchronous prologue,
|
|
45
|
+
* which covers `await harness.waitForIdle()` / `await harness.abort()`. A wait
|
|
46
|
+
* deferred behind an unrelated `await` inside the listener is not detected.
|
|
47
|
+
*/
|
|
48
|
+
assertNotSelfWait(api: string, currentOperationId: string | undefined): void;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=subscriber-fanout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscriber-fanout.d.ts","sourceRoot":"","sources":["../../src/harness/subscriber-fanout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,MAAM,MAAM,kBAAkB,CAAC,MAAM,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAEvG,qBAAa,gBAAgB,CAAC,MAAM,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE;IACrE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyC;IACnE,+EAA+E;IAC/E,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,SAAS,CAAqB;IAEtC,SAAS,CAAC,QAAQ,EAAE,kBAAkB,CAAC,MAAM,CAAC,GAAG,MAAM,IAAI,CAG1D;IAED;;;;;;;;;;OAUG;IACG,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BrG;IAED;;;;;;;OAOG;IACH,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAQ3E;CACD","sourcesContent":["/**\n * Subscriber fan-out with a self-wait barrier, extracted from `AgentHarness`.\n *\n * A listener that awaits the end of the very operation whose emission it is\n * blocking forms a cycle. The call that closes that cycle (`waitForIdle()`,\n * `abort()`) is always made during the listener's *synchronous* prologue — an\n * async function body runs synchronously up to its first `await` — so marking\n * just that window identifies callback-originated waits exactly, without\n * falsely rejecting an unrelated concurrent caller that runs later while the\n * listener's promise is merely pending.\n *\n * Delivery is observational: every subscriber runs even when an earlier one\n * fails, and the failures are raised together afterwards.\n *\n * This module imports no harness or session types so it stays a leaf for the\n * import-cycle ratchet; the harness passes the current operation id in.\n */\n\nimport { AgentHarnessError } from \"./errors.ts\";\nimport { normalizeHarnessError } from \"./operation-outcome.ts\";\n\nexport type SubscriberListener<TEvent> = (event: TEvent, signal?: AbortSignal) => Promise<void> | void;\n\nexport class SubscriberFanout<TEvent extends { readonly type: string }> {\n\tprivate readonly listeners = new Set<SubscriberListener<TEvent>>();\n\t/** Non-zero only while a subscriber's synchronous prologue is on the stack. */\n\tprivate syncFrameDepth = 0;\n\tprivate operationId: string | undefined;\n\tprivate eventType: string | undefined;\n\n\tsubscribe(listener: SubscriberListener<TEvent>): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => this.listeners.delete(listener);\n\t}\n\n\t/**\n\t * Deliver `event` to every subscriber in registration order, then fail the\n\t * emission once the fanout has completed.\n\t *\n\t * Observational subscribers must not starve each other: one throwing telemetry\n\t * or audit listener cannot hide `settled` / `attempt_finished` from the\n\t * listeners registered after it. Failures are collected and raised after the\n\t * last listener ran — a single failure keeps its own classification, several\n\t * become one `AggregateError` classified by the first. Policy and mutation\n\t * hooks live on `AgentHarness.on()` and stay fail-fast.\n\t */\n\tasync emit(event: TEvent, currentOperationId: string | undefined, signal?: AbortSignal): Promise<void> {\n\t\tconst failures: unknown[] = [];\n\t\tfor (const listener of this.listeners) {\n\t\t\tconst previousOperationId = this.operationId;\n\t\t\tconst previousEventType = this.eventType;\n\t\t\tthis.operationId = currentOperationId;\n\t\t\tthis.eventType = event.type;\n\t\t\tlet pending: Promise<unknown> | unknown;\n\t\t\tthis.syncFrameDepth += 1;\n\t\t\ttry {\n\t\t\t\tpending = listener(event, signal);\n\t\t\t} catch (error) {\n\t\t\t\tfailures.push(error);\n\t\t\t} finally {\n\t\t\t\tthis.syncFrameDepth -= 1;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait pending;\n\t\t\t} catch (error) {\n\t\t\t\tfailures.push(error);\n\t\t\t} finally {\n\t\t\t\tthis.operationId = previousOperationId;\n\t\t\t\tthis.eventType = previousEventType;\n\t\t\t}\n\t\t}\n\t\tif (failures.length === 1) throw normalizeHarnessError(failures[0], \"hook\");\n\t\tif (failures.length > 1) {\n\t\t\tconst cause = new AggregateError(failures, `${failures.length} subscribers failed for ${event.type}`);\n\t\t\tthrow new AgentHarnessError(normalizeHarnessError(failures[0], \"hook\").code, cause.message, cause);\n\t\t}\n\t}\n\n\t/**\n\t * Fail closed when an awaited listener tries to wait on its own operation.\n\t * Rejecting immediately turns a permanent deadlock into a classified error.\n\t *\n\t * Scope: this catches a wait issued from the listener's synchronous prologue,\n\t * which covers `await harness.waitForIdle()` / `await harness.abort()`. A wait\n\t * deferred behind an unrelated `await` inside the listener is not detected.\n\t */\n\tassertNotSelfWait(api: string, currentOperationId: string | undefined): void {\n\t\tif (this.syncFrameDepth > 0 && currentOperationId !== undefined && currentOperationId === this.operationId) {\n\t\t\tthrow new AgentHarnessError(\n\t\t\t\t\"invalid_state\",\n\t\t\t\t`${api} cannot await the current operation from an awaited ${this.eventType ?? \"unknown\"} callback; ` +\n\t\t\t\t\t\"use runWhenIdle() to schedule work for after it settles\",\n\t\t\t);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subscriber fan-out with a self-wait barrier, extracted from `AgentHarness`.
|
|
3
|
+
*
|
|
4
|
+
* A listener that awaits the end of the very operation whose emission it is
|
|
5
|
+
* blocking forms a cycle. The call that closes that cycle (`waitForIdle()`,
|
|
6
|
+
* `abort()`) is always made during the listener's *synchronous* prologue — an
|
|
7
|
+
* async function body runs synchronously up to its first `await` — so marking
|
|
8
|
+
* just that window identifies callback-originated waits exactly, without
|
|
9
|
+
* falsely rejecting an unrelated concurrent caller that runs later while the
|
|
10
|
+
* listener's promise is merely pending.
|
|
11
|
+
*
|
|
12
|
+
* Delivery is observational: every subscriber runs even when an earlier one
|
|
13
|
+
* fails, and the failures are raised together afterwards.
|
|
14
|
+
*
|
|
15
|
+
* This module imports no harness or session types so it stays a leaf for the
|
|
16
|
+
* import-cycle ratchet; the harness passes the current operation id in.
|
|
17
|
+
*/
|
|
18
|
+
import { AgentHarnessError } from "./errors.js";
|
|
19
|
+
import { normalizeHarnessError } from "./operation-outcome.js";
|
|
20
|
+
export class SubscriberFanout {
|
|
21
|
+
listeners = new Set();
|
|
22
|
+
/** Non-zero only while a subscriber's synchronous prologue is on the stack. */
|
|
23
|
+
syncFrameDepth = 0;
|
|
24
|
+
operationId;
|
|
25
|
+
eventType;
|
|
26
|
+
subscribe(listener) {
|
|
27
|
+
this.listeners.add(listener);
|
|
28
|
+
return () => this.listeners.delete(listener);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Deliver `event` to every subscriber in registration order, then fail the
|
|
32
|
+
* emission once the fanout has completed.
|
|
33
|
+
*
|
|
34
|
+
* Observational subscribers must not starve each other: one throwing telemetry
|
|
35
|
+
* or audit listener cannot hide `settled` / `attempt_finished` from the
|
|
36
|
+
* listeners registered after it. Failures are collected and raised after the
|
|
37
|
+
* last listener ran — a single failure keeps its own classification, several
|
|
38
|
+
* become one `AggregateError` classified by the first. Policy and mutation
|
|
39
|
+
* hooks live on `AgentHarness.on()` and stay fail-fast.
|
|
40
|
+
*/
|
|
41
|
+
async emit(event, currentOperationId, signal) {
|
|
42
|
+
const failures = [];
|
|
43
|
+
for (const listener of this.listeners) {
|
|
44
|
+
const previousOperationId = this.operationId;
|
|
45
|
+
const previousEventType = this.eventType;
|
|
46
|
+
this.operationId = currentOperationId;
|
|
47
|
+
this.eventType = event.type;
|
|
48
|
+
let pending;
|
|
49
|
+
this.syncFrameDepth += 1;
|
|
50
|
+
try {
|
|
51
|
+
pending = listener(event, signal);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
failures.push(error);
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
this.syncFrameDepth -= 1;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
await pending;
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
failures.push(error);
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
this.operationId = previousOperationId;
|
|
67
|
+
this.eventType = previousEventType;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (failures.length === 1)
|
|
71
|
+
throw normalizeHarnessError(failures[0], "hook");
|
|
72
|
+
if (failures.length > 1) {
|
|
73
|
+
const cause = new AggregateError(failures, `${failures.length} subscribers failed for ${event.type}`);
|
|
74
|
+
throw new AgentHarnessError(normalizeHarnessError(failures[0], "hook").code, cause.message, cause);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Fail closed when an awaited listener tries to wait on its own operation.
|
|
79
|
+
* Rejecting immediately turns a permanent deadlock into a classified error.
|
|
80
|
+
*
|
|
81
|
+
* Scope: this catches a wait issued from the listener's synchronous prologue,
|
|
82
|
+
* which covers `await harness.waitForIdle()` / `await harness.abort()`. A wait
|
|
83
|
+
* deferred behind an unrelated `await` inside the listener is not detected.
|
|
84
|
+
*/
|
|
85
|
+
assertNotSelfWait(api, currentOperationId) {
|
|
86
|
+
if (this.syncFrameDepth > 0 && currentOperationId !== undefined && currentOperationId === this.operationId) {
|
|
87
|
+
throw new AgentHarnessError("invalid_state", `${api} cannot await the current operation from an awaited ${this.eventType ?? "unknown"} callback; ` +
|
|
88
|
+
"use runWhenIdle() to schedule work for after it settles");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=subscriber-fanout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscriber-fanout.js","sourceRoot":"","sources":["../../src/harness/subscriber-fanout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAI/D,MAAM,OAAO,gBAAgB;IACX,SAAS,GAAG,IAAI,GAAG,EAA8B,CAAC;IACnE,+EAA+E;IACvE,cAAc,GAAG,CAAC,CAAC;IACnB,WAAW,CAAqB;IAChC,SAAS,CAAqB;IAEtC,SAAS,CAAC,QAAoC,EAAc;QAC3D,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAAA,CAC7C;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,IAAI,CAAC,KAAa,EAAE,kBAAsC,EAAE,MAAoB,EAAiB;QACtG,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACvC,MAAM,mBAAmB,GAAG,IAAI,CAAC,WAAW,CAAC;YAC7C,MAAM,iBAAiB,GAAG,IAAI,CAAC,SAAS,CAAC;YACzC,IAAI,CAAC,WAAW,GAAG,kBAAkB,CAAC;YACtC,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC;YAC5B,IAAI,OAAmC,CAAC;YACxC,IAAI,CAAC,cAAc,IAAI,CAAC,CAAC;YACzB,IAAI,CAAC;gBACJ,OAAO,GAAG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YACnC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACtB,CAAC;oBAAS,CAAC;gBACV,IAAI,CAAC,cAAc,IAAI,CAAC,CAAC;YAC1B,CAAC;YACD,IAAI,CAAC;gBACJ,MAAM,OAAO,CAAC;YACf,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACtB,CAAC;oBAAS,CAAC;gBACV,IAAI,CAAC,WAAW,GAAG,mBAAmB,CAAC;gBACvC,IAAI,CAAC,SAAS,GAAG,iBAAiB,CAAC;YACpC,CAAC;QACF,CAAC;QACD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,qBAAqB,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QAC5E,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,KAAK,GAAG,IAAI,cAAc,CAAC,QAAQ,EAAE,GAAG,QAAQ,CAAC,MAAM,2BAA2B,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YACtG,MAAM,IAAI,iBAAiB,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACpG,CAAC;IAAA,CACD;IAED;;;;;;;OAOG;IACH,iBAAiB,CAAC,GAAW,EAAE,kBAAsC,EAAQ;QAC5E,IAAI,IAAI,CAAC,cAAc,GAAG,CAAC,IAAI,kBAAkB,KAAK,SAAS,IAAI,kBAAkB,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC;YAC5G,MAAM,IAAI,iBAAiB,CAC1B,eAAe,EACf,GAAG,GAAG,uDAAuD,IAAI,CAAC,SAAS,IAAI,SAAS,aAAa;gBACpG,yDAAyD,CAC1D,CAAC;QACH,CAAC;IAAA,CACD;CACD","sourcesContent":["/**\n * Subscriber fan-out with a self-wait barrier, extracted from `AgentHarness`.\n *\n * A listener that awaits the end of the very operation whose emission it is\n * blocking forms a cycle. The call that closes that cycle (`waitForIdle()`,\n * `abort()`) is always made during the listener's *synchronous* prologue — an\n * async function body runs synchronously up to its first `await` — so marking\n * just that window identifies callback-originated waits exactly, without\n * falsely rejecting an unrelated concurrent caller that runs later while the\n * listener's promise is merely pending.\n *\n * Delivery is observational: every subscriber runs even when an earlier one\n * fails, and the failures are raised together afterwards.\n *\n * This module imports no harness or session types so it stays a leaf for the\n * import-cycle ratchet; the harness passes the current operation id in.\n */\n\nimport { AgentHarnessError } from \"./errors.ts\";\nimport { normalizeHarnessError } from \"./operation-outcome.ts\";\n\nexport type SubscriberListener<TEvent> = (event: TEvent, signal?: AbortSignal) => Promise<void> | void;\n\nexport class SubscriberFanout<TEvent extends { readonly type: string }> {\n\tprivate readonly listeners = new Set<SubscriberListener<TEvent>>();\n\t/** Non-zero only while a subscriber's synchronous prologue is on the stack. */\n\tprivate syncFrameDepth = 0;\n\tprivate operationId: string | undefined;\n\tprivate eventType: string | undefined;\n\n\tsubscribe(listener: SubscriberListener<TEvent>): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => this.listeners.delete(listener);\n\t}\n\n\t/**\n\t * Deliver `event` to every subscriber in registration order, then fail the\n\t * emission once the fanout has completed.\n\t *\n\t * Observational subscribers must not starve each other: one throwing telemetry\n\t * or audit listener cannot hide `settled` / `attempt_finished` from the\n\t * listeners registered after it. Failures are collected and raised after the\n\t * last listener ran — a single failure keeps its own classification, several\n\t * become one `AggregateError` classified by the first. Policy and mutation\n\t * hooks live on `AgentHarness.on()` and stay fail-fast.\n\t */\n\tasync emit(event: TEvent, currentOperationId: string | undefined, signal?: AbortSignal): Promise<void> {\n\t\tconst failures: unknown[] = [];\n\t\tfor (const listener of this.listeners) {\n\t\t\tconst previousOperationId = this.operationId;\n\t\t\tconst previousEventType = this.eventType;\n\t\t\tthis.operationId = currentOperationId;\n\t\t\tthis.eventType = event.type;\n\t\t\tlet pending: Promise<unknown> | unknown;\n\t\t\tthis.syncFrameDepth += 1;\n\t\t\ttry {\n\t\t\t\tpending = listener(event, signal);\n\t\t\t} catch (error) {\n\t\t\t\tfailures.push(error);\n\t\t\t} finally {\n\t\t\t\tthis.syncFrameDepth -= 1;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait pending;\n\t\t\t} catch (error) {\n\t\t\t\tfailures.push(error);\n\t\t\t} finally {\n\t\t\t\tthis.operationId = previousOperationId;\n\t\t\t\tthis.eventType = previousEventType;\n\t\t\t}\n\t\t}\n\t\tif (failures.length === 1) throw normalizeHarnessError(failures[0], \"hook\");\n\t\tif (failures.length > 1) {\n\t\t\tconst cause = new AggregateError(failures, `${failures.length} subscribers failed for ${event.type}`);\n\t\t\tthrow new AgentHarnessError(normalizeHarnessError(failures[0], \"hook\").code, cause.message, cause);\n\t\t}\n\t}\n\n\t/**\n\t * Fail closed when an awaited listener tries to wait on its own operation.\n\t * Rejecting immediately turns a permanent deadlock into a classified error.\n\t *\n\t * Scope: this catches a wait issued from the listener's synchronous prologue,\n\t * which covers `await harness.waitForIdle()` / `await harness.abort()`. A wait\n\t * deferred behind an unrelated `await` inside the listener is not detected.\n\t */\n\tassertNotSelfWait(api: string, currentOperationId: string | undefined): void {\n\t\tif (this.syncFrameDepth > 0 && currentOperationId !== undefined && currentOperationId === this.operationId) {\n\t\t\tthrow new AgentHarnessError(\n\t\t\t\t\"invalid_state\",\n\t\t\t\t`${api} cannot await the current operation from an awaited ${this.eventType ?? \"unknown\"} callback; ` +\n\t\t\t\t\t\"use runWhenIdle() to schedule work for after it settles\",\n\t\t\t);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree-navigation helpers extracted from `AgentHarness.navigateTree`.
|
|
3
|
+
*
|
|
4
|
+
* These cover the two self-contained steps of a navigation — deciding where to
|
|
5
|
+
* land, and producing a branch summary — so the harness method is left with
|
|
6
|
+
* lifecycle staging, hook dispatch, and the session commit.
|
|
7
|
+
*/
|
|
8
|
+
import type { Model, RetryPolicy } from "omk-ai";
|
|
9
|
+
import { type SummarizationRetryEvent } from "./summarization-retry.ts";
|
|
10
|
+
import type { SessionTreeEntry } from "./types.ts";
|
|
11
|
+
export interface NavigationTarget {
|
|
12
|
+
/** Leaf the session should move to. */
|
|
13
|
+
readonly newLeafId: string | null;
|
|
14
|
+
/** Original text of a user-authored target, handed back for re-editing. */
|
|
15
|
+
readonly editorText?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Where a navigation lands. Targeting a user-authored entry rewinds to its
|
|
19
|
+
* parent and returns that entry's text, so the caller can edit and resend it;
|
|
20
|
+
* any other entry is entered directly.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveNavigationTarget(targetEntry: SessionTreeEntry, targetId: string): NavigationTarget;
|
|
23
|
+
export type BranchSummaryOutcome = {
|
|
24
|
+
readonly cancelled: true;
|
|
25
|
+
} | {
|
|
26
|
+
readonly cancelled: false;
|
|
27
|
+
readonly summary: string;
|
|
28
|
+
readonly details: unknown;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Summarize the entries being navigated away from. An aborted summary is a
|
|
32
|
+
* cancellation, not a failure; every other summarization error is classified
|
|
33
|
+
* as `branch_summary`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function runBranchSummary(input: {
|
|
36
|
+
readonly entries: SessionTreeEntry[];
|
|
37
|
+
readonly model: Model<any>;
|
|
38
|
+
readonly apiKey: string;
|
|
39
|
+
readonly headers?: Record<string, string>;
|
|
40
|
+
readonly customInstructions?: string;
|
|
41
|
+
readonly replaceInstructions?: boolean;
|
|
42
|
+
readonly summarizationRetry: RetryPolicy | undefined;
|
|
43
|
+
readonly emit: (event: SummarizationRetryEvent) => Promise<void> | void;
|
|
44
|
+
}): Promise<BranchSummaryOutcome>;
|
|
45
|
+
//# sourceMappingURL=tree-navigation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-navigation.d.ts","sourceRoot":"","sources":["../../src/harness/tree-navigation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAgB,KAAK,EAAE,WAAW,EAAe,MAAM,QAAQ,CAAC;AAE5E,OAAO,EAA4B,KAAK,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAClG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAWnD,MAAM,WAAW,gBAAgB;IAChC,uCAAuC;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,2EAA2E;IAC3E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,gBAAgB,CAQzG;AAED,MAAM,MAAM,oBAAoB,GAC7B;IAAE,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtF;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE;IAC7C,QAAQ,CAAC,OAAO,EAAE,gBAAgB,EAAE,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IACvC,QAAQ,CAAC,kBAAkB,EAAE,WAAW,GAAG,SAAS,CAAC;IACrD,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxE,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAmBhC","sourcesContent":["/**\n * Tree-navigation helpers extracted from `AgentHarness.navigateTree`.\n *\n * These cover the two self-contained steps of a navigation — deciding where to\n * land, and producing a branch summary — so the harness method is left with\n * lifecycle staging, hook dispatch, and the session commit.\n */\n\nimport type { ImageContent, Model, RetryPolicy, TextContent } from \"omk-ai\";\nimport { generateBranchSummary } from \"./compaction/branch-summarization.ts\";\nimport { createSummarizationRetry, type SummarizationRetryEvent } from \"./summarization-retry.ts\";\nimport type { SessionTreeEntry } from \"./types.ts\";\nimport { AgentHarnessError } from \"./types.ts\";\n\nfunction flattenText(content: string | (TextContent | ImageContent)[]): string {\n\tif (typeof content === \"string\") return content;\n\treturn content\n\t\t.filter((part): part is TextContent => part.type === \"text\")\n\t\t.map((part) => part.text)\n\t\t.join(\"\");\n}\n\nexport interface NavigationTarget {\n\t/** Leaf the session should move to. */\n\treadonly newLeafId: string | null;\n\t/** Original text of a user-authored target, handed back for re-editing. */\n\treadonly editorText?: string;\n}\n\n/**\n * Where a navigation lands. Targeting a user-authored entry rewinds to its\n * parent and returns that entry's text, so the caller can edit and resend it;\n * any other entry is entered directly.\n */\nexport function resolveNavigationTarget(targetEntry: SessionTreeEntry, targetId: string): NavigationTarget {\n\tif (targetEntry.type === \"message\" && targetEntry.message.role === \"user\") {\n\t\treturn { newLeafId: targetEntry.parentId, editorText: flattenText(targetEntry.message.content) };\n\t}\n\tif (targetEntry.type === \"custom_message\") {\n\t\treturn { newLeafId: targetEntry.parentId, editorText: flattenText(targetEntry.content) };\n\t}\n\treturn { newLeafId: targetId };\n}\n\nexport type BranchSummaryOutcome =\n\t| { readonly cancelled: true }\n\t| { readonly cancelled: false; readonly summary: string; readonly details: unknown };\n\n/**\n * Summarize the entries being navigated away from. An aborted summary is a\n * cancellation, not a failure; every other summarization error is classified\n * as `branch_summary`.\n */\nexport async function runBranchSummary(input: {\n\treadonly entries: SessionTreeEntry[];\n\treadonly model: Model<any>;\n\treadonly apiKey: string;\n\treadonly headers?: Record<string, string>;\n\treadonly customInstructions?: string;\n\treadonly replaceInstructions?: boolean;\n\treadonly summarizationRetry: RetryPolicy | undefined;\n\treadonly emit: (event: SummarizationRetryEvent) => Promise<void> | void;\n}): Promise<BranchSummaryOutcome> {\n\tconst result = await generateBranchSummary(input.entries, {\n\t\tmodel: input.model,\n\t\tapiKey: input.apiKey,\n\t\theaders: input.headers,\n\t\tsignal: new AbortController().signal,\n\t\tcustomInstructions: input.customInstructions,\n\t\treplaceInstructions: input.replaceInstructions,\n\t\t...createSummarizationRetry(\"branch_summary\", input.summarizationRetry, input.emit),\n\t});\n\tif (!result.ok) {\n\t\tif (result.error.code === \"aborted\") return { cancelled: true };\n\t\tthrow new AgentHarnessError(\"branch_summary\", result.error.message, result.error);\n\t}\n\treturn {\n\t\tcancelled: false,\n\t\tsummary: result.value.summary,\n\t\tdetails: { readFiles: result.value.readFiles, modifiedFiles: result.value.modifiedFiles },\n\t};\n}\n"]}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree-navigation helpers extracted from `AgentHarness.navigateTree`.
|
|
3
|
+
*
|
|
4
|
+
* These cover the two self-contained steps of a navigation — deciding where to
|
|
5
|
+
* land, and producing a branch summary — so the harness method is left with
|
|
6
|
+
* lifecycle staging, hook dispatch, and the session commit.
|
|
7
|
+
*/
|
|
8
|
+
import { generateBranchSummary } from "./compaction/branch-summarization.js";
|
|
9
|
+
import { createSummarizationRetry } from "./summarization-retry.js";
|
|
10
|
+
import { AgentHarnessError } from "./types.js";
|
|
11
|
+
function flattenText(content) {
|
|
12
|
+
if (typeof content === "string")
|
|
13
|
+
return content;
|
|
14
|
+
return content
|
|
15
|
+
.filter((part) => part.type === "text")
|
|
16
|
+
.map((part) => part.text)
|
|
17
|
+
.join("");
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Where a navigation lands. Targeting a user-authored entry rewinds to its
|
|
21
|
+
* parent and returns that entry's text, so the caller can edit and resend it;
|
|
22
|
+
* any other entry is entered directly.
|
|
23
|
+
*/
|
|
24
|
+
export function resolveNavigationTarget(targetEntry, targetId) {
|
|
25
|
+
if (targetEntry.type === "message" && targetEntry.message.role === "user") {
|
|
26
|
+
return { newLeafId: targetEntry.parentId, editorText: flattenText(targetEntry.message.content) };
|
|
27
|
+
}
|
|
28
|
+
if (targetEntry.type === "custom_message") {
|
|
29
|
+
return { newLeafId: targetEntry.parentId, editorText: flattenText(targetEntry.content) };
|
|
30
|
+
}
|
|
31
|
+
return { newLeafId: targetId };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Summarize the entries being navigated away from. An aborted summary is a
|
|
35
|
+
* cancellation, not a failure; every other summarization error is classified
|
|
36
|
+
* as `branch_summary`.
|
|
37
|
+
*/
|
|
38
|
+
export async function runBranchSummary(input) {
|
|
39
|
+
const result = await generateBranchSummary(input.entries, {
|
|
40
|
+
model: input.model,
|
|
41
|
+
apiKey: input.apiKey,
|
|
42
|
+
headers: input.headers,
|
|
43
|
+
signal: new AbortController().signal,
|
|
44
|
+
customInstructions: input.customInstructions,
|
|
45
|
+
replaceInstructions: input.replaceInstructions,
|
|
46
|
+
...createSummarizationRetry("branch_summary", input.summarizationRetry, input.emit),
|
|
47
|
+
});
|
|
48
|
+
if (!result.ok) {
|
|
49
|
+
if (result.error.code === "aborted")
|
|
50
|
+
return { cancelled: true };
|
|
51
|
+
throw new AgentHarnessError("branch_summary", result.error.message, result.error);
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
cancelled: false,
|
|
55
|
+
summary: result.value.summary,
|
|
56
|
+
details: { readFiles: result.value.readFiles, modifiedFiles: result.value.modifiedFiles },
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=tree-navigation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-navigation.js","sourceRoot":"","sources":["../../src/harness/tree-navigation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,EAAE,wBAAwB,EAAgC,MAAM,0BAA0B,CAAC;AAElG,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE/C,SAAS,WAAW,CAAC,OAAgD,EAAU;IAC9E,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IAChD,OAAO,OAAO;SACZ,MAAM,CAAC,CAAC,IAAI,EAAuB,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC;SAC3D,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;SACxB,IAAI,CAAC,EAAE,CAAC,CAAC;AAAA,CACX;AASD;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CAAC,WAA6B,EAAE,QAAgB,EAAoB;IAC1G,IAAI,WAAW,CAAC,IAAI,KAAK,SAAS,IAAI,WAAW,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC3E,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC,QAAQ,EAAE,UAAU,EAAE,WAAW,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;IAClG,CAAC;IACD,IAAI,WAAW,CAAC,IAAI,KAAK,gBAAgB,EAAE,CAAC;QAC3C,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC,QAAQ,EAAE,UAAU,EAAE,WAAW,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;IAC1F,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;AAAA,CAC/B;AAMD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,KAStC,EAAiC;IACjC,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC,KAAK,CAAC,OAAO,EAAE;QACzD,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,MAAM,EAAE,IAAI,eAAe,EAAE,CAAC,MAAM;QACpC,kBAAkB,EAAE,KAAK,CAAC,kBAAkB;QAC5C,mBAAmB,EAAE,KAAK,CAAC,mBAAmB;QAC9C,GAAG,wBAAwB,CAAC,gBAAgB,EAAE,KAAK,CAAC,kBAAkB,EAAE,KAAK,CAAC,IAAI,CAAC;KACnF,CAAC,CAAC;IACH,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QAChB,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS;YAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;QAChE,MAAM,IAAI,iBAAiB,CAAC,gBAAgB,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IACnF,CAAC;IACD,OAAO;QACN,SAAS,EAAE,KAAK;QAChB,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO;QAC7B,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,aAAa,EAAE,MAAM,CAAC,KAAK,CAAC,aAAa,EAAE;KACzF,CAAC;AAAA,CACF","sourcesContent":["/**\n * Tree-navigation helpers extracted from `AgentHarness.navigateTree`.\n *\n * These cover the two self-contained steps of a navigation — deciding where to\n * land, and producing a branch summary — so the harness method is left with\n * lifecycle staging, hook dispatch, and the session commit.\n */\n\nimport type { ImageContent, Model, RetryPolicy, TextContent } from \"omk-ai\";\nimport { generateBranchSummary } from \"./compaction/branch-summarization.ts\";\nimport { createSummarizationRetry, type SummarizationRetryEvent } from \"./summarization-retry.ts\";\nimport type { SessionTreeEntry } from \"./types.ts\";\nimport { AgentHarnessError } from \"./types.ts\";\n\nfunction flattenText(content: string | (TextContent | ImageContent)[]): string {\n\tif (typeof content === \"string\") return content;\n\treturn content\n\t\t.filter((part): part is TextContent => part.type === \"text\")\n\t\t.map((part) => part.text)\n\t\t.join(\"\");\n}\n\nexport interface NavigationTarget {\n\t/** Leaf the session should move to. */\n\treadonly newLeafId: string | null;\n\t/** Original text of a user-authored target, handed back for re-editing. */\n\treadonly editorText?: string;\n}\n\n/**\n * Where a navigation lands. Targeting a user-authored entry rewinds to its\n * parent and returns that entry's text, so the caller can edit and resend it;\n * any other entry is entered directly.\n */\nexport function resolveNavigationTarget(targetEntry: SessionTreeEntry, targetId: string): NavigationTarget {\n\tif (targetEntry.type === \"message\" && targetEntry.message.role === \"user\") {\n\t\treturn { newLeafId: targetEntry.parentId, editorText: flattenText(targetEntry.message.content) };\n\t}\n\tif (targetEntry.type === \"custom_message\") {\n\t\treturn { newLeafId: targetEntry.parentId, editorText: flattenText(targetEntry.content) };\n\t}\n\treturn { newLeafId: targetId };\n}\n\nexport type BranchSummaryOutcome =\n\t| { readonly cancelled: true }\n\t| { readonly cancelled: false; readonly summary: string; readonly details: unknown };\n\n/**\n * Summarize the entries being navigated away from. An aborted summary is a\n * cancellation, not a failure; every other summarization error is classified\n * as `branch_summary`.\n */\nexport async function runBranchSummary(input: {\n\treadonly entries: SessionTreeEntry[];\n\treadonly model: Model<any>;\n\treadonly apiKey: string;\n\treadonly headers?: Record<string, string>;\n\treadonly customInstructions?: string;\n\treadonly replaceInstructions?: boolean;\n\treadonly summarizationRetry: RetryPolicy | undefined;\n\treadonly emit: (event: SummarizationRetryEvent) => Promise<void> | void;\n}): Promise<BranchSummaryOutcome> {\n\tconst result = await generateBranchSummary(input.entries, {\n\t\tmodel: input.model,\n\t\tapiKey: input.apiKey,\n\t\theaders: input.headers,\n\t\tsignal: new AbortController().signal,\n\t\tcustomInstructions: input.customInstructions,\n\t\treplaceInstructions: input.replaceInstructions,\n\t\t...createSummarizationRetry(\"branch_summary\", input.summarizationRetry, input.emit),\n\t});\n\tif (!result.ok) {\n\t\tif (result.error.code === \"aborted\") return { cancelled: true };\n\t\tthrow new AgentHarnessError(\"branch_summary\", result.error.message, result.error);\n\t}\n\treturn {\n\t\tcancelled: false,\n\t\tsummary: result.value.summary,\n\t\tdetails: { readFiles: result.value.readFiles, modifiedFiles: result.value.modifiedFiles },\n\t};\n}\n"]}
|
package/dist/harness/types.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ImageContent, Model, TextContent } from "omk-ai";
|
|
2
|
-
import type { AgentEvent, AgentMessage, AgentTool, QueueMode, ThinkingLevel } from "../
|
|
2
|
+
import type { AgentEvent, AgentMessage, AgentTool, QueueMode, ThinkingLevel } from "../types.ts";
|
|
3
3
|
export { AgentHarnessError, type AgentHarnessErrorCode, SessionError, type SessionErrorCode, toError, } from "./errors.ts";
|
|
4
|
+
import type { HarnessAttemptRef, HarnessAttemptSummary, HarnessOperationOutcome, HarnessOperationRef } from "./operation-lifecycle-types.ts";
|
|
4
5
|
import type { Session } from "./session/session.ts";
|
|
5
6
|
import type { AgentHarnessStreamOptions, AgentHarnessStreamOptionsPatch } from "./stream-options.ts";
|
|
6
7
|
import type { SummarizationRetryEvent } from "./summarization-retry.ts";
|
|
@@ -275,6 +276,12 @@ export interface JsonlSessionMetadata extends SessionMetadata {
|
|
|
275
276
|
path: string;
|
|
276
277
|
parentSessionPath?: string;
|
|
277
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Durable session storage adapter.
|
|
281
|
+
*
|
|
282
|
+
* Mutation methods are non-reentrant: while a storage mutation is unsettled, its implementation must not call or await
|
|
283
|
+
* mutators on the owning `Session`, `HarnessSession`, or `AgentHarness`.
|
|
284
|
+
*/
|
|
278
285
|
export interface SessionStorage<TMetadata extends SessionMetadata = SessionMetadata> {
|
|
279
286
|
getMetadata(): Promise<TMetadata>;
|
|
280
287
|
getLeafId(): Promise<string | null>;
|
|
@@ -315,6 +322,11 @@ export interface JsonlSessionListOptions {
|
|
|
315
322
|
}
|
|
316
323
|
export interface JsonlSessionRepoApi extends SessionRepo<JsonlSessionMetadata, JsonlSessionCreateOptions, JsonlSessionListOptions> {
|
|
317
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* @deprecated Internal facade-gate vocabulary. Operation lifecycle state is
|
|
327
|
+
* owned by `OperationLifecycleController`; this union remains only as the
|
|
328
|
+
* session facade's write-gate mapping and will be removed at 1.0.
|
|
329
|
+
*/
|
|
318
330
|
export type AgentHarnessPhase = "idle" | "turn" | "compaction" | "branch_summary" | "retry";
|
|
319
331
|
export type PendingSessionWrite = SessionTreeEntry extends infer TEntry ? TEntry extends SessionTreeEntry ? Omit<TEntry, "id" | "parentId" | "timestamp"> : never : never;
|
|
320
332
|
export interface HarnessCustomMessageInput<T = unknown> {
|
|
@@ -359,6 +371,23 @@ export interface AbortEvent {
|
|
|
359
371
|
export interface SettledEvent {
|
|
360
372
|
type: "settled";
|
|
361
373
|
nextTurnCount: number;
|
|
374
|
+
/** Identity of the public operation that just settled exactly once. */
|
|
375
|
+
operationId: string;
|
|
376
|
+
outcome: HarnessOperationOutcome;
|
|
377
|
+
attemptCount: number;
|
|
378
|
+
}
|
|
379
|
+
export interface OperationStartedEvent {
|
|
380
|
+
type: "operation_started";
|
|
381
|
+
operation: HarnessOperationRef;
|
|
382
|
+
}
|
|
383
|
+
export interface AttemptStartedEvent {
|
|
384
|
+
type: "attempt_started";
|
|
385
|
+
attempt: HarnessAttemptRef;
|
|
386
|
+
}
|
|
387
|
+
/** Emitted once per low-level agent attempt; carries a bounded summary, never a transcript. */
|
|
388
|
+
export interface AttemptFinishedEvent {
|
|
389
|
+
type: "attempt_finished";
|
|
390
|
+
summary: HarnessAttemptSummary;
|
|
362
391
|
}
|
|
363
392
|
export interface BeforeAgentStartEvent<TSkill extends Skill = Skill, TPromptTemplate extends PromptTemplate = PromptTemplate> {
|
|
364
393
|
type: "before_agent_start";
|
|
@@ -450,7 +479,7 @@ export interface ResourcesUpdateEvent<TSkill extends Skill = Skill, TPromptTempl
|
|
|
450
479
|
resources: AgentHarnessResources<TSkill, TPromptTemplate>;
|
|
451
480
|
previousResources: AgentHarnessResources<TSkill, TPromptTemplate>;
|
|
452
481
|
}
|
|
453
|
-
export type AgentHarnessOwnEvent<TSkill extends Skill = Skill, TPromptTemplate extends PromptTemplate = PromptTemplate> = QueueUpdateEvent | SavePointEvent | AbortEvent | SettledEvent | BeforeAgentStartEvent<TSkill, TPromptTemplate> | ContextEvent | BeforeProviderRequestEvent | BeforeProviderPayloadEvent | AfterProviderResponseEvent | ToolCallEvent | ToolResultEvent | SessionBeforeCompactEvent | SessionCompactEvent | SessionBeforeTreeEvent | SessionTreeEvent | SummarizationRetryEvent | ModelUpdateEvent | ThinkingLevelUpdateEvent | ResourcesUpdateEvent<TSkill, TPromptTemplate> | ToolsUpdateEvent;
|
|
482
|
+
export type AgentHarnessOwnEvent<TSkill extends Skill = Skill, TPromptTemplate extends PromptTemplate = PromptTemplate> = QueueUpdateEvent | SavePointEvent | AbortEvent | SettledEvent | OperationStartedEvent | AttemptStartedEvent | AttemptFinishedEvent | BeforeAgentStartEvent<TSkill, TPromptTemplate> | ContextEvent | BeforeProviderRequestEvent | BeforeProviderPayloadEvent | AfterProviderResponseEvent | ToolCallEvent | ToolResultEvent | SessionBeforeCompactEvent | SessionCompactEvent | SessionBeforeTreeEvent | SessionTreeEvent | SummarizationRetryEvent | ModelUpdateEvent | ThinkingLevelUpdateEvent | ResourcesUpdateEvent<TSkill, TPromptTemplate> | ToolsUpdateEvent;
|
|
454
483
|
export type AgentHarnessEvent<TSkill extends Skill = Skill, TPromptTemplate extends PromptTemplate = PromptTemplate> = AgentEvent | AgentHarnessOwnEvent<TSkill, TPromptTemplate>;
|
|
455
484
|
export interface BeforeAgentStartResult {
|
|
456
485
|
messages?: AgentMessage[];
|
|
@@ -512,6 +541,9 @@ export type AgentHarnessEventResultMap = {
|
|
|
512
541
|
save_point: undefined;
|
|
513
542
|
abort: undefined;
|
|
514
543
|
settled: undefined;
|
|
544
|
+
operation_started: undefined;
|
|
545
|
+
attempt_started: undefined;
|
|
546
|
+
attempt_finished: undefined;
|
|
515
547
|
};
|
|
516
548
|
export interface AgentHarnessPromptOptions {
|
|
517
549
|
images?: ImageContent[];
|
|
@@ -607,5 +639,4 @@ export interface AgentHarnessOptions<TSkill extends Skill = Skill, TPromptTempla
|
|
|
607
639
|
steeringMode?: QueueMode;
|
|
608
640
|
followUpMode?: QueueMode;
|
|
609
641
|
}
|
|
610
|
-
export type { AgentHarness } from "./agent-harness.ts";
|
|
611
642
|
//# sourceMappingURL=types.d.ts.map
|