omk-agent-core 0.98.0 → 0.98.2
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/dist/harness/agent-harness.d.ts +29 -14
- package/dist/harness/agent-harness.d.ts.map +1 -1
- package/dist/harness/agent-harness.js +409 -453
- package/dist/harness/agent-harness.js.map +1 -1
- package/dist/harness/compaction/branch-summarization.d.ts +5 -1
- package/dist/harness/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/harness/compaction/branch-summarization.js +3 -3
- package/dist/harness/compaction/branch-summarization.js.map +1 -1
- package/dist/harness/compaction/compaction.d.ts +8 -4
- package/dist/harness/compaction/compaction.d.ts.map +1 -1
- package/dist/harness/compaction/compaction.js +12 -97
- package/dist/harness/compaction/compaction.js.map +1 -1
- package/dist/harness/compaction/operation.d.ts +13 -0
- package/dist/harness/compaction/operation.d.ts.map +1 -0
- package/dist/harness/compaction/operation.js +2 -0
- package/dist/harness/compaction/operation.js.map +1 -0
- package/dist/harness/compaction/summarization-prompts.d.ts +5 -0
- package/dist/harness/compaction/summarization-prompts.d.ts.map +1 -0
- package/dist/harness/compaction/summarization-prompts.js +88 -0
- package/dist/harness/compaction/summarization-prompts.js.map +1 -0
- package/dist/harness/errors.d.ts +15 -0
- package/dist/harness/errors.d.ts.map +1 -0
- package/dist/harness/errors.js +32 -0
- package/dist/harness/errors.js.map +1 -0
- package/dist/harness/harness-session.d.ts +52 -0
- package/dist/harness/harness-session.d.ts.map +1 -0
- package/dist/harness/harness-session.js +113 -0
- package/dist/harness/harness-session.js.map +1 -0
- package/dist/harness/messages.d.ts +3 -1
- package/dist/harness/messages.d.ts.map +1 -1
- package/dist/harness/messages.js +26 -0
- package/dist/harness/messages.js.map +1 -1
- package/dist/harness/name-validation.d.ts +2 -0
- package/dist/harness/name-validation.d.ts.map +1 -0
- package/dist/harness/name-validation.js +11 -0
- package/dist/harness/name-validation.js.map +1 -0
- 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 +71 -0
- package/dist/harness/operation-outcome.d.ts.map +1 -0
- package/dist/harness/operation-outcome.js +131 -0
- package/dist/harness/operation-outcome.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/stream-options.d.ts +31 -0
- package/dist/harness/stream-options.d.ts.map +1 -0
- package/dist/harness/stream-options.js +66 -0
- package/dist/harness/stream-options.js.map +1 -0
- package/dist/harness/subscriber-fanout.d.ts +37 -0
- package/dist/harness/subscriber-fanout.d.ts.map +1 -0
- package/dist/harness/subscriber-fanout.js +73 -0
- package/dist/harness/subscriber-fanout.js.map +1 -0
- package/dist/harness/summarization-retry.d.ts +29 -0
- package/dist/harness/summarization-retry.d.ts.map +1 -0
- package/dist/harness/summarization-retry.js +20 -0
- package/dist/harness/summarization-retry.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 +69 -43
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js +1 -32
- package/dist/harness/types.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { createImmutableSnapshot } from "../plain-data.js";
|
|
2
|
+
import { AgentHarnessError, toError } from "./errors.js";
|
|
3
|
+
/**
|
|
4
|
+
* Internal implementation of the public `HarnessSession` structural interface.
|
|
5
|
+
*
|
|
6
|
+
* This module intentionally imports no harness/session types. `AgentHarness` and the
|
|
7
|
+
* raw session are in a legacy import-cycle component; importing their types here would
|
|
8
|
+
* pull this new boundary into that component because the cycle ratchet treats static
|
|
9
|
+
* type imports as architectural edges. Generic ports keep the implementation a leaf.
|
|
10
|
+
*/
|
|
11
|
+
export class HarnessSessionFacade {
|
|
12
|
+
session;
|
|
13
|
+
getPhase;
|
|
14
|
+
sessionWrites;
|
|
15
|
+
constructor(session, getPhase, sessionWrites) {
|
|
16
|
+
this.session = session;
|
|
17
|
+
this.getPhase = getPhase;
|
|
18
|
+
this.sessionWrites = sessionWrites;
|
|
19
|
+
}
|
|
20
|
+
async getMetadata() {
|
|
21
|
+
return this.read(() => this.session.getMetadata());
|
|
22
|
+
}
|
|
23
|
+
async getLeafId() {
|
|
24
|
+
return this.read(() => this.session.getLeafId());
|
|
25
|
+
}
|
|
26
|
+
async getEntry(id) {
|
|
27
|
+
return this.read(() => this.session.getEntry(id));
|
|
28
|
+
}
|
|
29
|
+
async getEntries() {
|
|
30
|
+
return this.read(() => this.session.getEntries());
|
|
31
|
+
}
|
|
32
|
+
async getBranch(fromId) {
|
|
33
|
+
return this.read(() => this.session.getBranch(fromId));
|
|
34
|
+
}
|
|
35
|
+
async buildContext() {
|
|
36
|
+
return this.read(() => this.session.buildContext());
|
|
37
|
+
}
|
|
38
|
+
async getLabel(id) {
|
|
39
|
+
return this.read(() => this.session.getLabel(id));
|
|
40
|
+
}
|
|
41
|
+
async getSessionName() {
|
|
42
|
+
return this.read(() => this.session.getSessionName());
|
|
43
|
+
}
|
|
44
|
+
getPendingWrites() {
|
|
45
|
+
return this.sessionWrites.snapshot();
|
|
46
|
+
}
|
|
47
|
+
async appendMessage(message) {
|
|
48
|
+
await this.write(this.snapshot({ type: "message", message }));
|
|
49
|
+
}
|
|
50
|
+
async appendCustomEntry(customType, data) {
|
|
51
|
+
await this.write(this.snapshot({ type: "custom", customType, data }));
|
|
52
|
+
}
|
|
53
|
+
async appendCustomMessage(input) {
|
|
54
|
+
const write = this.snapshot({
|
|
55
|
+
type: "custom_message",
|
|
56
|
+
customType: input.customType,
|
|
57
|
+
content: typeof input.content === "string" ? input.content : [...input.content],
|
|
58
|
+
display: input.display,
|
|
59
|
+
details: input.details,
|
|
60
|
+
});
|
|
61
|
+
await this.write(write);
|
|
62
|
+
}
|
|
63
|
+
async appendLabel(targetId, label) {
|
|
64
|
+
if (!(await this.getEntry(targetId))) {
|
|
65
|
+
throw new AgentHarnessError("invalid_argument", `Entry ${targetId} not found`);
|
|
66
|
+
}
|
|
67
|
+
await this.write(this.snapshot({ type: "label", targetId, label }));
|
|
68
|
+
}
|
|
69
|
+
async appendSessionName(name) {
|
|
70
|
+
await this.write(this.snapshot({ type: "session_info", name: name.trim() }));
|
|
71
|
+
}
|
|
72
|
+
async setLeafId(targetId) {
|
|
73
|
+
if (targetId !== null && !(await this.getEntry(targetId))) {
|
|
74
|
+
throw new AgentHarnessError("invalid_argument", `Entry ${targetId} not found`);
|
|
75
|
+
}
|
|
76
|
+
await this.write(this.snapshot({ type: "leaf", targetId }));
|
|
77
|
+
}
|
|
78
|
+
async read(operation) {
|
|
79
|
+
try {
|
|
80
|
+
return createImmutableSnapshot(await operation());
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
throw new AgentHarnessError("session", "Session read failed", toError(error));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
snapshot(write) {
|
|
87
|
+
try {
|
|
88
|
+
return createImmutableSnapshot(write);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
throw new AgentHarnessError("invalid_argument", "Session writes must contain plain data", toError(error));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async write(write) {
|
|
95
|
+
const phase = this.getPhase();
|
|
96
|
+
if (phase === "turn") {
|
|
97
|
+
this.sessionWrites.enqueue(write);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (phase !== "idle") {
|
|
101
|
+
throw new AgentHarnessError("invalid_state", `Cannot write session state during ${phase}`);
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
await this.sessionWrites.persistAfterPending(write);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (error instanceof AgentHarnessError && error.code === "invalid_state")
|
|
108
|
+
throw error;
|
|
109
|
+
throw new AgentHarnessError("session", "Session write failed", toError(error));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=harness-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harness-session.js","sourceRoot":"","sources":["../../src/harness/harness-session.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ImageContent, TextContent } from \"omk-ai\";\nimport { createImmutableSnapshot } from \"../plain-data.ts\";\nimport { AgentHarnessError, toError } from \"./errors.ts\";\nimport type { QueuedSessionWrite, SessionWriteCoordinator } from \"./session-write-coordinator.ts\";\n\ntype FacadeWrite<TMessage> =\n\t| { readonly type: \"message\"; readonly message: TMessage }\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 SessionPort<TEntry, TContext, TMetadata> {\n\tgetMetadata(): Promise<TMetadata>;\n\tgetLeafId(): Promise<string | null>;\n\tgetEntry(id: string): Promise<TEntry | undefined>;\n\tgetEntries(): Promise<TEntry[]>;\n\tgetBranch(fromId?: string): Promise<TEntry[]>;\n\tbuildContext(): Promise<TContext>;\n\tgetLabel(id: string): Promise<string | undefined>;\n\tgetSessionName(): Promise<string | undefined>;\n}\n\ninterface CustomMessageInput<T = unknown> {\n\treadonly customType: string;\n\treadonly content: string | readonly (TextContent | ImageContent)[];\n\treadonly display: boolean;\n\treadonly details?: T;\n}\n\n/**\n * Internal implementation of the public `HarnessSession` structural interface.\n *\n * This module intentionally imports no harness/session types. `AgentHarness` and the\n * raw session are in a legacy import-cycle component; importing their types here would\n * pull this new boundary into that component because the cycle ratchet treats static\n * type imports as architectural edges. Generic ports keep the implementation a leaf.\n */\nexport class HarnessSessionFacade<TMessage, TEntry, TContext, TMetadata> {\n\tprivate readonly session: SessionPort<TEntry, TContext, TMetadata>;\n\tprivate readonly getPhase: () => string;\n\tprivate readonly sessionWrites: SessionWriteCoordinator<TMessage>;\n\n\tconstructor(\n\t\tsession: SessionPort<TEntry, TContext, TMetadata>,\n\t\tgetPhase: () => string,\n\t\tsessionWrites: SessionWriteCoordinator<TMessage>,\n\t) {\n\t\tthis.session = session;\n\t\tthis.getPhase = getPhase;\n\t\tthis.sessionWrites = sessionWrites;\n\t}\n\n\tasync getMetadata(): Promise<Readonly<TMetadata>> {\n\t\treturn this.read(() => this.session.getMetadata());\n\t}\n\n\tasync getLeafId(): Promise<string | null> {\n\t\treturn this.read(() => this.session.getLeafId());\n\t}\n\n\tasync getEntry(id: string): Promise<Readonly<TEntry> | undefined> {\n\t\treturn this.read(() => this.session.getEntry(id));\n\t}\n\n\tasync getEntries(): Promise<readonly Readonly<TEntry>[]> {\n\t\treturn this.read(() => this.session.getEntries());\n\t}\n\n\tasync getBranch(fromId?: string): Promise<readonly Readonly<TEntry>[]> {\n\t\treturn this.read(() => this.session.getBranch(fromId));\n\t}\n\n\tasync buildContext(): Promise<Readonly<TContext>> {\n\t\treturn this.read(() => this.session.buildContext());\n\t}\n\n\tasync getLabel(id: string): Promise<string | undefined> {\n\t\treturn this.read(() => this.session.getLabel(id));\n\t}\n\n\tasync getSessionName(): Promise<string | undefined> {\n\t\treturn this.read(() => this.session.getSessionName());\n\t}\n\n\tgetPendingWrites(): readonly QueuedSessionWrite<TMessage>[] {\n\t\treturn this.sessionWrites.snapshot();\n\t}\n\n\tasync appendMessage(message: TMessage): Promise<void> {\n\t\tawait this.write(this.snapshot({ type: \"message\", message }));\n\t}\n\n\tasync appendCustomEntry(customType: string, data?: unknown): Promise<void> {\n\t\tawait this.write(this.snapshot({ type: \"custom\", customType, data }));\n\t}\n\n\tasync appendCustomMessage<T = unknown>(input: CustomMessageInput<T>): Promise<void> {\n\t\tconst write = this.snapshot({\n\t\t\ttype: \"custom_message\",\n\t\t\tcustomType: input.customType,\n\t\t\tcontent: typeof input.content === \"string\" ? input.content : [...input.content],\n\t\t\tdisplay: input.display,\n\t\t\tdetails: input.details,\n\t\t});\n\t\tawait this.write(write);\n\t}\n\n\tasync appendLabel(targetId: string, label: string | undefined): Promise<void> {\n\t\tif (!(await this.getEntry(targetId))) {\n\t\t\tthrow new AgentHarnessError(\"invalid_argument\", `Entry ${targetId} not found`);\n\t\t}\n\t\tawait this.write(this.snapshot({ type: \"label\", targetId, label }));\n\t}\n\n\tasync appendSessionName(name: string): Promise<void> {\n\t\tawait this.write(this.snapshot({ type: \"session_info\", name: name.trim() }));\n\t}\n\n\tasync setLeafId(targetId: string | null): Promise<void> {\n\t\tif (targetId !== null && !(await this.getEntry(targetId))) {\n\t\t\tthrow new AgentHarnessError(\"invalid_argument\", `Entry ${targetId} not found`);\n\t\t}\n\t\tawait this.write(this.snapshot({ type: \"leaf\", targetId }));\n\t}\n\n\tprivate async read<T>(operation: () => Promise<T>): Promise<T> {\n\t\ttry {\n\t\t\treturn createImmutableSnapshot(await operation());\n\t\t} catch (error) {\n\t\t\tthrow new AgentHarnessError(\"session\", \"Session read failed\", toError(error));\n\t\t}\n\t}\n\n\tprivate snapshot<TWrite extends FacadeWrite<TMessage>>(write: TWrite): TWrite {\n\t\ttry {\n\t\t\treturn createImmutableSnapshot(write);\n\t\t} catch (error) {\n\t\t\tthrow new AgentHarnessError(\"invalid_argument\", \"Session writes must contain plain data\", toError(error));\n\t\t}\n\t}\n\n\tprivate async write(write: FacadeWrite<TMessage>): Promise<void> {\n\t\tconst phase = this.getPhase();\n\t\tif (phase === \"turn\") {\n\t\t\tthis.sessionWrites.enqueue(write);\n\t\t\treturn;\n\t\t}\n\t\tif (phase !== \"idle\") {\n\t\t\tthrow new AgentHarnessError(\"invalid_state\", `Cannot write session state during ${phase}`);\n\t\t}\n\t\ttry {\n\t\t\tawait this.sessionWrites.persistAfterPending(write);\n\t\t} catch (error) {\n\t\t\tif (error instanceof AgentHarnessError && error.code === \"invalid_state\") throw error;\n\t\t\tthrow new AgentHarnessError(\"session\", \"Session write failed\", toError(error));\n\t\t}\n\t}\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ImageContent, Message, TextContent } from "omk-ai";
|
|
1
|
+
import type { AssistantMessage, ImageContent, Message, Model, TextContent, UserMessage } from "omk-ai";
|
|
2
2
|
import type { AgentMessage } from "../types.ts";
|
|
3
3
|
export declare const COMPACTION_SUMMARY_PREFIX = "The conversation history before this point was compacted into the following summary:\n\n<summary>\n";
|
|
4
4
|
export declare const COMPACTION_SUMMARY_SUFFIX = "\n</summary>";
|
|
@@ -43,6 +43,8 @@ declare module "../types.ts" {
|
|
|
43
43
|
compactionSummary: CompactionSummaryMessage;
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
+
export declare function createUserMessage(text: string, images?: ImageContent[]): UserMessage;
|
|
47
|
+
export declare function createFailureMessage(model: Model<any>, error: unknown, aborted: boolean): AssistantMessage;
|
|
46
48
|
export declare function bashExecutionToText(msg: BashExecutionMessage): string;
|
|
47
49
|
export declare function createBranchSummaryMessage(summary: string, fromId: string, timestamp: string): BranchSummaryMessage;
|
|
48
50
|
export declare function createCompactionSummaryMessage(summary: string, tokensBefore: number, timestamp: string): CompactionSummaryMessage;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/harness/messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/harness/messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACvG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,eAAO,MAAM,yBAAyB,wGAGrC,CAAC;AAEF,eAAO,MAAM,yBAAyB,iBAC3B,CAAC;AAEZ,eAAO,MAAM,qBAAqB,iGAGjC,CAAC;AAEF,eAAO,MAAM,qBAAqB,eAAe,CAAC;AAElD,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,GAAG,OAAO;IACzC,IAAI,EAAE,QAAQ,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,GAAG,CAAC,WAAW,GAAG,YAAY,CAAC,EAAE,CAAC;IACjD,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,CAAC,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,wBAAwB;IACxC,IAAI,EAAE,mBAAmB,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,OAAO,QAAQ,aAAa,CAAC,CAAC;IAC7B,UAAU,mBAAmB;QAC5B,aAAa,EAAE,oBAAoB,CAAC;QACpC,MAAM,EAAE,aAAa,CAAC;QACtB,aAAa,EAAE,oBAAoB,CAAC;QACpC,iBAAiB,EAAE,wBAAwB,CAAC;KAC5C;CACD;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,YAAY,EAAE,GAAG,WAAW,CAIpF;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,gBAAgB,CAmB1G;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,oBAAoB,GAAG,MAAM,CAgBrE;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,oBAAoB,CAOnH;AAED,wBAAgB,8BAA8B,CAC7C,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,GACf,wBAAwB,CAO1B;AAED,wBAAgB,mBAAmB,CAClC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GAAG,CAAC,WAAW,GAAG,YAAY,CAAC,EAAE,EAChD,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,OAAO,GAAG,SAAS,EAC5B,SAAS,EAAE,MAAM,GACf,aAAa,CASf;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,OAAO,EAAE,CA4ChE","sourcesContent":["import type { AssistantMessage, ImageContent, Message, Model, TextContent, UserMessage } from \"omk-ai\";\nimport type { AgentMessage } from \"../types.ts\";\n\nexport const COMPACTION_SUMMARY_PREFIX = `The conversation history before this point was compacted into the following summary:\n\n<summary>\n`;\n\nexport const COMPACTION_SUMMARY_SUFFIX = `\n</summary>`;\n\nexport const BRANCH_SUMMARY_PREFIX = `The following is a summary of a branch that this conversation came back from:\n\n<summary>\n`;\n\nexport const BRANCH_SUMMARY_SUFFIX = `</summary>`;\n\nexport interface BashExecutionMessage {\n\trole: \"bashExecution\";\n\tcommand: string;\n\toutput: string;\n\texitCode: number | undefined;\n\tcancelled: boolean;\n\ttruncated: boolean;\n\tfullOutputPath?: string;\n\ttimestamp: number;\n\texcludeFromContext?: boolean;\n}\n\nexport interface CustomMessage<T = unknown> {\n\trole: \"custom\";\n\tcustomType: string;\n\tcontent: string | (TextContent | ImageContent)[];\n\tdisplay: boolean;\n\tdetails?: T;\n\ttimestamp: number;\n}\n\nexport interface BranchSummaryMessage {\n\trole: \"branchSummary\";\n\tsummary: string;\n\tfromId: string;\n\ttimestamp: number;\n}\n\nexport interface CompactionSummaryMessage {\n\trole: \"compactionSummary\";\n\tsummary: string;\n\ttokensBefore: number;\n\ttimestamp: number;\n}\n\ndeclare module \"../types.ts\" {\n\tinterface CustomAgentMessages {\n\t\tbashExecution: BashExecutionMessage;\n\t\tcustom: CustomMessage;\n\t\tbranchSummary: BranchSummaryMessage;\n\t\tcompactionSummary: CompactionSummaryMessage;\n\t}\n}\n\nexport function createUserMessage(text: string, images?: ImageContent[]): UserMessage {\n\tconst content: Array<{ type: \"text\"; text: string } | ImageContent> = [{ type: \"text\", text }];\n\tif (images) content.push(...images);\n\treturn { role: \"user\", content, timestamp: Date.now() };\n}\n\nexport function createFailureMessage(model: Model<any>, error: unknown, aborted: boolean): AssistantMessage {\n\treturn {\n\t\trole: \"assistant\",\n\t\tcontent: [{ type: \"text\", text: \"\" }],\n\t\tapi: model.api,\n\t\tprovider: model.provider,\n\t\tmodel: model.id,\n\t\tstopReason: aborted ? \"aborted\" : \"error\",\n\t\terrorMessage: error instanceof Error ? error.message : String(error),\n\t\ttimestamp: Date.now(),\n\t\tusage: {\n\t\t\tinput: 0,\n\t\t\toutput: 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\ttotalTokens: 0,\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t},\n\t};\n}\n\nexport function bashExecutionToText(msg: BashExecutionMessage): string {\n\tlet text = `Ran \\`${msg.command}\\`\\n`;\n\tif (msg.output) {\n\t\ttext += `\\`\\`\\`\\n${msg.output}\\n\\`\\`\\``;\n\t} else {\n\t\ttext += \"(no output)\";\n\t}\n\tif (msg.cancelled) {\n\t\ttext += \"\\n\\n(command cancelled)\";\n\t} else if (msg.exitCode !== null && msg.exitCode !== undefined && msg.exitCode !== 0) {\n\t\ttext += `\\n\\nCommand exited with code ${msg.exitCode}`;\n\t}\n\tif (msg.truncated && msg.fullOutputPath) {\n\t\ttext += `\\n\\n[Output truncated. Full output: ${msg.fullOutputPath}]`;\n\t}\n\treturn text;\n}\n\nexport function createBranchSummaryMessage(summary: string, fromId: string, timestamp: string): BranchSummaryMessage {\n\treturn {\n\t\trole: \"branchSummary\",\n\t\tsummary,\n\t\tfromId,\n\t\ttimestamp: new Date(timestamp).getTime(),\n\t};\n}\n\nexport function createCompactionSummaryMessage(\n\tsummary: string,\n\ttokensBefore: number,\n\ttimestamp: string,\n): CompactionSummaryMessage {\n\treturn {\n\t\trole: \"compactionSummary\",\n\t\tsummary,\n\t\ttokensBefore,\n\t\ttimestamp: new Date(timestamp).getTime(),\n\t};\n}\n\nexport function createCustomMessage(\n\tcustomType: string,\n\tcontent: string | (TextContent | ImageContent)[],\n\tdisplay: boolean,\n\tdetails: unknown | undefined,\n\ttimestamp: string,\n): CustomMessage {\n\treturn {\n\t\trole: \"custom\",\n\t\tcustomType,\n\t\tcontent,\n\t\tdisplay,\n\t\tdetails,\n\t\ttimestamp: new Date(timestamp).getTime(),\n\t};\n}\n\nexport function convertToLlm(messages: AgentMessage[]): Message[] {\n\treturn messages\n\t\t.map((m): Message | undefined => {\n\t\t\tswitch (m.role) {\n\t\t\t\tcase \"bashExecution\":\n\t\t\t\t\tif (m.excludeFromContext) {\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\trole: \"user\",\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: bashExecutionToText(m) }],\n\t\t\t\t\t\ttimestamp: m.timestamp,\n\t\t\t\t\t};\n\t\t\t\tcase \"custom\": {\n\t\t\t\t\tconst content = typeof m.content === \"string\" ? [{ type: \"text\" as const, text: m.content }] : m.content;\n\t\t\t\t\treturn {\n\t\t\t\t\t\trole: \"user\",\n\t\t\t\t\t\tcontent,\n\t\t\t\t\t\ttimestamp: m.timestamp,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tcase \"branchSummary\":\n\t\t\t\t\treturn {\n\t\t\t\t\t\trole: \"user\",\n\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: BRANCH_SUMMARY_PREFIX + m.summary + BRANCH_SUMMARY_SUFFIX }],\n\t\t\t\t\t\ttimestamp: m.timestamp,\n\t\t\t\t\t};\n\t\t\t\tcase \"compactionSummary\":\n\t\t\t\t\treturn {\n\t\t\t\t\t\trole: \"user\",\n\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t{ type: \"text\" as const, text: COMPACTION_SUMMARY_PREFIX + m.summary + COMPACTION_SUMMARY_SUFFIX },\n\t\t\t\t\t\t],\n\t\t\t\t\t\ttimestamp: m.timestamp,\n\t\t\t\t\t};\n\t\t\t\tcase \"user\":\n\t\t\t\tcase \"assistant\":\n\t\t\t\tcase \"toolResult\":\n\t\t\t\t\treturn m;\n\t\t\t\tdefault:\n\t\t\t\t\treturn undefined;\n\t\t\t}\n\t\t})\n\t\t.filter((m): m is Message => m !== undefined);\n}\n"]}
|
package/dist/harness/messages.js
CHANGED
|
@@ -9,6 +9,32 @@ export const BRANCH_SUMMARY_PREFIX = `The following is a summary of a branch tha
|
|
|
9
9
|
<summary>
|
|
10
10
|
`;
|
|
11
11
|
export const BRANCH_SUMMARY_SUFFIX = `</summary>`;
|
|
12
|
+
export function createUserMessage(text, images) {
|
|
13
|
+
const content = [{ type: "text", text }];
|
|
14
|
+
if (images)
|
|
15
|
+
content.push(...images);
|
|
16
|
+
return { role: "user", content, timestamp: Date.now() };
|
|
17
|
+
}
|
|
18
|
+
export function createFailureMessage(model, error, aborted) {
|
|
19
|
+
return {
|
|
20
|
+
role: "assistant",
|
|
21
|
+
content: [{ type: "text", text: "" }],
|
|
22
|
+
api: model.api,
|
|
23
|
+
provider: model.provider,
|
|
24
|
+
model: model.id,
|
|
25
|
+
stopReason: aborted ? "aborted" : "error",
|
|
26
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
27
|
+
timestamp: Date.now(),
|
|
28
|
+
usage: {
|
|
29
|
+
input: 0,
|
|
30
|
+
output: 0,
|
|
31
|
+
cacheRead: 0,
|
|
32
|
+
cacheWrite: 0,
|
|
33
|
+
totalTokens: 0,
|
|
34
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
12
38
|
export function bashExecutionToText(msg) {
|
|
13
39
|
let text = `Ran \`${msg.command}\`\n`;
|
|
14
40
|
if (msg.output) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/harness/messages.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ImageContent, Message, TextContent } from \"omk-ai\";\nimport type { AgentMessage } from \"../types.ts\";\n\nexport const COMPACTION_SUMMARY_PREFIX = `The conversation history before this point was compacted into the following summary:\n\n<summary>\n`;\n\nexport const COMPACTION_SUMMARY_SUFFIX = `\n</summary>`;\n\nexport const BRANCH_SUMMARY_PREFIX = `The following is a summary of a branch that this conversation came back from:\n\n<summary>\n`;\n\nexport const BRANCH_SUMMARY_SUFFIX = `</summary>`;\n\nexport interface BashExecutionMessage {\n\trole: \"bashExecution\";\n\tcommand: string;\n\toutput: string;\n\texitCode: number | undefined;\n\tcancelled: boolean;\n\ttruncated: boolean;\n\tfullOutputPath?: string;\n\ttimestamp: number;\n\texcludeFromContext?: boolean;\n}\n\nexport interface CustomMessage<T = unknown> {\n\trole: \"custom\";\n\tcustomType: string;\n\tcontent: string | (TextContent | ImageContent)[];\n\tdisplay: boolean;\n\tdetails?: T;\n\ttimestamp: number;\n}\n\nexport interface BranchSummaryMessage {\n\trole: \"branchSummary\";\n\tsummary: string;\n\tfromId: string;\n\ttimestamp: number;\n}\n\nexport interface CompactionSummaryMessage {\n\trole: \"compactionSummary\";\n\tsummary: string;\n\ttokensBefore: number;\n\ttimestamp: number;\n}\n\ndeclare module \"../types.ts\" {\n\tinterface CustomAgentMessages {\n\t\tbashExecution: BashExecutionMessage;\n\t\tcustom: CustomMessage;\n\t\tbranchSummary: BranchSummaryMessage;\n\t\tcompactionSummary: CompactionSummaryMessage;\n\t}\n}\n\nexport function bashExecutionToText(msg: BashExecutionMessage): string {\n\tlet text = `Ran \\`${msg.command}\\`\\n`;\n\tif (msg.output) {\n\t\ttext += `\\`\\`\\`\\n${msg.output}\\n\\`\\`\\``;\n\t} else {\n\t\ttext += \"(no output)\";\n\t}\n\tif (msg.cancelled) {\n\t\ttext += \"\\n\\n(command cancelled)\";\n\t} else if (msg.exitCode !== null && msg.exitCode !== undefined && msg.exitCode !== 0) {\n\t\ttext += `\\n\\nCommand exited with code ${msg.exitCode}`;\n\t}\n\tif (msg.truncated && msg.fullOutputPath) {\n\t\ttext += `\\n\\n[Output truncated. Full output: ${msg.fullOutputPath}]`;\n\t}\n\treturn text;\n}\n\nexport function createBranchSummaryMessage(summary: string, fromId: string, timestamp: string): BranchSummaryMessage {\n\treturn {\n\t\trole: \"branchSummary\",\n\t\tsummary,\n\t\tfromId,\n\t\ttimestamp: new Date(timestamp).getTime(),\n\t};\n}\n\nexport function createCompactionSummaryMessage(\n\tsummary: string,\n\ttokensBefore: number,\n\ttimestamp: string,\n): CompactionSummaryMessage {\n\treturn {\n\t\trole: \"compactionSummary\",\n\t\tsummary,\n\t\ttokensBefore,\n\t\ttimestamp: new Date(timestamp).getTime(),\n\t};\n}\n\nexport function createCustomMessage(\n\tcustomType: string,\n\tcontent: string | (TextContent | ImageContent)[],\n\tdisplay: boolean,\n\tdetails: unknown | undefined,\n\ttimestamp: string,\n): CustomMessage {\n\treturn {\n\t\trole: \"custom\",\n\t\tcustomType,\n\t\tcontent,\n\t\tdisplay,\n\t\tdetails,\n\t\ttimestamp: new Date(timestamp).getTime(),\n\t};\n}\n\nexport function convertToLlm(messages: AgentMessage[]): Message[] {\n\treturn messages\n\t\t.map((m): Message | undefined => {\n\t\t\tswitch (m.role) {\n\t\t\t\tcase \"bashExecution\":\n\t\t\t\t\tif (m.excludeFromContext) {\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\trole: \"user\",\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: bashExecutionToText(m) }],\n\t\t\t\t\t\ttimestamp: m.timestamp,\n\t\t\t\t\t};\n\t\t\t\tcase \"custom\": {\n\t\t\t\t\tconst content = typeof m.content === \"string\" ? [{ type: \"text\" as const, text: m.content }] : m.content;\n\t\t\t\t\treturn {\n\t\t\t\t\t\trole: \"user\",\n\t\t\t\t\t\tcontent,\n\t\t\t\t\t\ttimestamp: m.timestamp,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tcase \"branchSummary\":\n\t\t\t\t\treturn {\n\t\t\t\t\t\trole: \"user\",\n\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: BRANCH_SUMMARY_PREFIX + m.summary + BRANCH_SUMMARY_SUFFIX }],\n\t\t\t\t\t\ttimestamp: m.timestamp,\n\t\t\t\t\t};\n\t\t\t\tcase \"compactionSummary\":\n\t\t\t\t\treturn {\n\t\t\t\t\t\trole: \"user\",\n\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t{ type: \"text\" as const, text: COMPACTION_SUMMARY_PREFIX + m.summary + COMPACTION_SUMMARY_SUFFIX },\n\t\t\t\t\t\t],\n\t\t\t\t\t\ttimestamp: m.timestamp,\n\t\t\t\t\t};\n\t\t\t\tcase \"user\":\n\t\t\t\tcase \"assistant\":\n\t\t\t\tcase \"toolResult\":\n\t\t\t\t\treturn m;\n\t\t\t\tdefault:\n\t\t\t\t\treturn undefined;\n\t\t\t}\n\t\t})\n\t\t.filter((m): m is Message => m !== undefined);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/harness/messages.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage, ImageContent, Message, Model, TextContent, UserMessage } from \"omk-ai\";\nimport type { AgentMessage } from \"../types.ts\";\n\nexport const COMPACTION_SUMMARY_PREFIX = `The conversation history before this point was compacted into the following summary:\n\n<summary>\n`;\n\nexport const COMPACTION_SUMMARY_SUFFIX = `\n</summary>`;\n\nexport const BRANCH_SUMMARY_PREFIX = `The following is a summary of a branch that this conversation came back from:\n\n<summary>\n`;\n\nexport const BRANCH_SUMMARY_SUFFIX = `</summary>`;\n\nexport interface BashExecutionMessage {\n\trole: \"bashExecution\";\n\tcommand: string;\n\toutput: string;\n\texitCode: number | undefined;\n\tcancelled: boolean;\n\ttruncated: boolean;\n\tfullOutputPath?: string;\n\ttimestamp: number;\n\texcludeFromContext?: boolean;\n}\n\nexport interface CustomMessage<T = unknown> {\n\trole: \"custom\";\n\tcustomType: string;\n\tcontent: string | (TextContent | ImageContent)[];\n\tdisplay: boolean;\n\tdetails?: T;\n\ttimestamp: number;\n}\n\nexport interface BranchSummaryMessage {\n\trole: \"branchSummary\";\n\tsummary: string;\n\tfromId: string;\n\ttimestamp: number;\n}\n\nexport interface CompactionSummaryMessage {\n\trole: \"compactionSummary\";\n\tsummary: string;\n\ttokensBefore: number;\n\ttimestamp: number;\n}\n\ndeclare module \"../types.ts\" {\n\tinterface CustomAgentMessages {\n\t\tbashExecution: BashExecutionMessage;\n\t\tcustom: CustomMessage;\n\t\tbranchSummary: BranchSummaryMessage;\n\t\tcompactionSummary: CompactionSummaryMessage;\n\t}\n}\n\nexport function createUserMessage(text: string, images?: ImageContent[]): UserMessage {\n\tconst content: Array<{ type: \"text\"; text: string } | ImageContent> = [{ type: \"text\", text }];\n\tif (images) content.push(...images);\n\treturn { role: \"user\", content, timestamp: Date.now() };\n}\n\nexport function createFailureMessage(model: Model<any>, error: unknown, aborted: boolean): AssistantMessage {\n\treturn {\n\t\trole: \"assistant\",\n\t\tcontent: [{ type: \"text\", text: \"\" }],\n\t\tapi: model.api,\n\t\tprovider: model.provider,\n\t\tmodel: model.id,\n\t\tstopReason: aborted ? \"aborted\" : \"error\",\n\t\terrorMessage: error instanceof Error ? error.message : String(error),\n\t\ttimestamp: Date.now(),\n\t\tusage: {\n\t\t\tinput: 0,\n\t\t\toutput: 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\ttotalTokens: 0,\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t},\n\t};\n}\n\nexport function bashExecutionToText(msg: BashExecutionMessage): string {\n\tlet text = `Ran \\`${msg.command}\\`\\n`;\n\tif (msg.output) {\n\t\ttext += `\\`\\`\\`\\n${msg.output}\\n\\`\\`\\``;\n\t} else {\n\t\ttext += \"(no output)\";\n\t}\n\tif (msg.cancelled) {\n\t\ttext += \"\\n\\n(command cancelled)\";\n\t} else if (msg.exitCode !== null && msg.exitCode !== undefined && msg.exitCode !== 0) {\n\t\ttext += `\\n\\nCommand exited with code ${msg.exitCode}`;\n\t}\n\tif (msg.truncated && msg.fullOutputPath) {\n\t\ttext += `\\n\\n[Output truncated. Full output: ${msg.fullOutputPath}]`;\n\t}\n\treturn text;\n}\n\nexport function createBranchSummaryMessage(summary: string, fromId: string, timestamp: string): BranchSummaryMessage {\n\treturn {\n\t\trole: \"branchSummary\",\n\t\tsummary,\n\t\tfromId,\n\t\ttimestamp: new Date(timestamp).getTime(),\n\t};\n}\n\nexport function createCompactionSummaryMessage(\n\tsummary: string,\n\ttokensBefore: number,\n\ttimestamp: string,\n): CompactionSummaryMessage {\n\treturn {\n\t\trole: \"compactionSummary\",\n\t\tsummary,\n\t\ttokensBefore,\n\t\ttimestamp: new Date(timestamp).getTime(),\n\t};\n}\n\nexport function createCustomMessage(\n\tcustomType: string,\n\tcontent: string | (TextContent | ImageContent)[],\n\tdisplay: boolean,\n\tdetails: unknown | undefined,\n\ttimestamp: string,\n): CustomMessage {\n\treturn {\n\t\trole: \"custom\",\n\t\tcustomType,\n\t\tcontent,\n\t\tdisplay,\n\t\tdetails,\n\t\ttimestamp: new Date(timestamp).getTime(),\n\t};\n}\n\nexport function convertToLlm(messages: AgentMessage[]): Message[] {\n\treturn messages\n\t\t.map((m): Message | undefined => {\n\t\t\tswitch (m.role) {\n\t\t\t\tcase \"bashExecution\":\n\t\t\t\t\tif (m.excludeFromContext) {\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\trole: \"user\",\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: bashExecutionToText(m) }],\n\t\t\t\t\t\ttimestamp: m.timestamp,\n\t\t\t\t\t};\n\t\t\t\tcase \"custom\": {\n\t\t\t\t\tconst content = typeof m.content === \"string\" ? [{ type: \"text\" as const, text: m.content }] : m.content;\n\t\t\t\t\treturn {\n\t\t\t\t\t\trole: \"user\",\n\t\t\t\t\t\tcontent,\n\t\t\t\t\t\ttimestamp: m.timestamp,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tcase \"branchSummary\":\n\t\t\t\t\treturn {\n\t\t\t\t\t\trole: \"user\",\n\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: BRANCH_SUMMARY_PREFIX + m.summary + BRANCH_SUMMARY_SUFFIX }],\n\t\t\t\t\t\ttimestamp: m.timestamp,\n\t\t\t\t\t};\n\t\t\t\tcase \"compactionSummary\":\n\t\t\t\t\treturn {\n\t\t\t\t\t\trole: \"user\",\n\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t{ type: \"text\" as const, text: COMPACTION_SUMMARY_PREFIX + m.summary + COMPACTION_SUMMARY_SUFFIX },\n\t\t\t\t\t\t],\n\t\t\t\t\t\ttimestamp: m.timestamp,\n\t\t\t\t\t};\n\t\t\t\tcase \"user\":\n\t\t\t\tcase \"assistant\":\n\t\t\t\tcase \"toolResult\":\n\t\t\t\t\treturn m;\n\t\t\t\tdefault:\n\t\t\t\t\treturn undefined;\n\t\t\t}\n\t\t})\n\t\t.filter((m): m is Message => m !== undefined);\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"name-validation.d.ts","sourceRoot":"","sources":["../../src/harness/name-validation.ts"],"names":[],"mappings":"AAAA,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAQrE","sourcesContent":["export function findDuplicateNames(names: readonly string[]): string[] {\n\tconst seen = new Set<string>();\n\tconst duplicates = new Set<string>();\n\tfor (const name of names) {\n\t\tif (seen.has(name)) duplicates.add(name);\n\t\tseen.add(name);\n\t}\n\treturn [...duplicates];\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function findDuplicateNames(names) {
|
|
2
|
+
const seen = new Set();
|
|
3
|
+
const duplicates = new Set();
|
|
4
|
+
for (const name of names) {
|
|
5
|
+
if (seen.has(name))
|
|
6
|
+
duplicates.add(name);
|
|
7
|
+
seen.add(name);
|
|
8
|
+
}
|
|
9
|
+
return [...duplicates];
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=name-validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"name-validation.js","sourceRoot":"","sources":["../../src/harness/name-validation.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,kBAAkB,CAAC,KAAwB,EAAY;IACtE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IACrC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC;IACD,OAAO,CAAC,GAAG,UAAU,CAAC,CAAC;AAAA,CACvB","sourcesContent":["export function findDuplicateNames(names: readonly string[]): string[] {\n\tconst seen = new Set<string>();\n\tconst duplicates = new Set<string>();\n\tfor (const name of names) {\n\t\tif (seen.has(name)) duplicates.add(name);\n\t\tseen.add(name);\n\t}\n\treturn [...duplicates];\n}\n"]}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owns the single mutable copy of the harness lifecycle state.
|
|
3
|
+
*
|
|
4
|
+
* The controller is the only writer: it builds commands, runs them through
|
|
5
|
+
* the pure reducer, and performs the side effects the reducer is forbidden
|
|
6
|
+
* from touching — clock reads, ID allocation, abort-signal delivery, and
|
|
7
|
+
* settlement of the operation's `settled` promise.
|
|
8
|
+
*
|
|
9
|
+
* Lease discipline: every mutating method requires the `OperationLease`
|
|
10
|
+
* returned by `begin`. A lease that is not the current record fails closed,
|
|
11
|
+
* so a stale async continuation from a finished operation can never mutate
|
|
12
|
+
* the state of a newer one. `settle()` is the only path from `active` to
|
|
13
|
+
* `idle`, runs the caller's finalizer inside the `settling` barrier, and is
|
|
14
|
+
* guarded against double invocation at both controller and reducer level.
|
|
15
|
+
*
|
|
16
|
+
* The `settled` promise resolves with the recorded outcome and never
|
|
17
|
+
* rejects; finalizer failures surface only through the `settle()` rejection,
|
|
18
|
+
* so callers cannot strand an unhandled rejection by ignoring `lease.settled`.
|
|
19
|
+
*/
|
|
20
|
+
import type { HarnessAttemptOutcome, HarnessAttemptRef, HarnessAttemptSummary, HarnessLifecycleCommand, HarnessLifecycleDependencies, HarnessLifecycleState, HarnessOperationKind, HarnessOperationOutcome, HarnessOperationRef } from "./operation-lifecycle-types.ts";
|
|
21
|
+
export interface OperationLease {
|
|
22
|
+
readonly operation: HarnessOperationRef;
|
|
23
|
+
readonly signal: AbortSignal;
|
|
24
|
+
/** Resolves once with the recorded outcome when the operation finishes settling. Never rejects. */
|
|
25
|
+
readonly settled: Promise<HarnessOperationOutcome>;
|
|
26
|
+
}
|
|
27
|
+
export interface AttemptLease {
|
|
28
|
+
readonly attempt: HarnessAttemptRef;
|
|
29
|
+
/** The owning operation's signal. Attempts never own a separate AbortController. */
|
|
30
|
+
readonly signal: AbortSignal;
|
|
31
|
+
}
|
|
32
|
+
export interface HarnessAbortCapture {
|
|
33
|
+
readonly target?: OperationLease;
|
|
34
|
+
readonly signalDelivered: boolean;
|
|
35
|
+
}
|
|
36
|
+
export declare class OperationLifecycleController {
|
|
37
|
+
private readonly deps;
|
|
38
|
+
private state;
|
|
39
|
+
private current?;
|
|
40
|
+
private readonly idleWaiters;
|
|
41
|
+
constructor(deps: HarnessLifecycleDependencies);
|
|
42
|
+
getSnapshot(): Readonly<HarnessLifecycleState>;
|
|
43
|
+
getCurrentOperation(): HarnessOperationRef | undefined;
|
|
44
|
+
begin(kind: HarnessOperationKind): OperationLease;
|
|
45
|
+
setStage(lease: OperationLease, stage: Extract<HarnessLifecycleCommand, {
|
|
46
|
+
type: "stage";
|
|
47
|
+
}>["stage"]): void;
|
|
48
|
+
beginAttempt(lease: OperationLease, reason: HarnessAttemptRef["reason"]): AttemptLease;
|
|
49
|
+
finishAttempt(lease: OperationLease, attempt: AttemptLease, outcome: HarnessAttemptOutcome): void;
|
|
50
|
+
/** Captures the current operation as the abort target; never targets operations started later. */
|
|
51
|
+
requestAbort(): HarnessAbortCapture;
|
|
52
|
+
/**
|
|
53
|
+
* Sole active -> idle path. Runs `finalize` inside the settling barrier, then
|
|
54
|
+
* releases state and resolves the lease. A finalizer failure still completes
|
|
55
|
+
* the state release and resolves `lease.settled`, then rejects this call.
|
|
56
|
+
*/
|
|
57
|
+
settle(lease: OperationLease, outcome: HarnessOperationOutcome, finalize: () => Promise<void>): Promise<HarnessOperationOutcome>;
|
|
58
|
+
/** Bounded attempt summaries for the current operation; no transcripts. */
|
|
59
|
+
getAttemptSummaries(lease: OperationLease): readonly HarnessAttemptSummary[];
|
|
60
|
+
/** The recorded summary for one attempt of the current operation. */
|
|
61
|
+
getAttemptSummary(lease: OperationLease, attempt: AttemptLease): HarnessAttemptSummary;
|
|
62
|
+
waitForIdle(): Promise<void>;
|
|
63
|
+
private expectActive;
|
|
64
|
+
private expectCurrentLease;
|
|
65
|
+
private apply;
|
|
66
|
+
private asHarnessError;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=operation-lifecycle-controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operation-lifecycle-controller.d.ts","sourceRoot":"","sources":["../../src/harness/operation-lifecycle-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH,OAAO,KAAK,EACX,qBAAqB,EACrB,iBAAiB,EACjB,qBAAqB,EACrB,uBAAuB,EACvB,4BAA4B,EAC5B,qBAAqB,EAErB,oBAAoB,EACpB,uBAAuB,EACvB,mBAAmB,EACnB,MAAM,gCAAgC,CAAC;AAExC,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,SAAS,EAAE,mBAAmB,CAAC;IACxC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,mGAAmG;IACnG,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,uBAAuB,CAAC,CAAC;CACnD;AAED,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACpC,oFAAoF;IACpF,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC7B;AAED,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;CAClC;AA4BD,qBAAa,4BAA4B;IACxC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA+B;IACpD,OAAO,CAAC,KAAK,CAA+E;IAC5F,OAAO,CAAC,OAAO,CAAC,CAAkB;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyB;IAErD,YAAY,IAAI,EAAE,4BAA4B,EAE7C;IAED,WAAW,IAAI,QAAQ,CAAC,qBAAqB,CAAC,CAE7C;IAED,mBAAmB,IAAI,mBAAmB,GAAG,SAAS,CAErD;IAED,KAAK,CAAC,IAAI,EAAE,oBAAoB,GAAG,cAAc,CAmBhD;IAED,QAAQ,CAAC,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,OAAO,CAAC,uBAAuB,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC,OAAO,CAAC,GAAG,IAAI,CAGzG;IAED,YAAY,CAAC,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,iBAAiB,CAAC,QAAQ,CAAC,GAAG,YAAY,CAarF;IAED,aAAa,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,qBAAqB,GAAG,IAAI,CAIhG;IAED,kGAAkG;IAClG,YAAY,IAAI,mBAAmB,CAWlC;IAED;;;;OAIG;IACG,MAAM,CACX,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,uBAAuB,EAChC,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAC3B,OAAO,CAAC,uBAAuB,CAAC,CA0BlC;IAED,2EAA2E;IAC3E,mBAAmB,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,qBAAqB,EAAE,CAW3E;IAED,qEAAqE;IACrE,iBAAiB,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,YAAY,GAAG,qBAAqB,CAOrF;IAEK,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAKjC;IAED,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,KAAK;IAMb,OAAO,CAAC,cAAc;CAGtB","sourcesContent":["/**\n * Owns the single mutable copy of the harness lifecycle state.\n *\n * The controller is the only writer: it builds commands, runs them through\n * the pure reducer, and performs the side effects the reducer is forbidden\n * from touching — clock reads, ID allocation, abort-signal delivery, and\n * settlement of the operation's `settled` promise.\n *\n * Lease discipline: every mutating method requires the `OperationLease`\n * returned by `begin`. A lease that is not the current record fails closed,\n * so a stale async continuation from a finished operation can never mutate\n * the state of a newer one. `settle()` is the only path from `active` to\n * `idle`, runs the caller's finalizer inside the `settling` barrier, and is\n * guarded against double invocation at both controller and reducer level.\n *\n * The `settled` promise resolves with the recorded outcome and never\n * rejects; finalizer failures surface only through the `settle()` rejection,\n * so callers cannot strand an unhandled rejection by ignoring `lease.settled`.\n */\n\nimport { createImmutableSnapshot } from \"../plain-data.ts\";\nimport { AgentHarnessError } from \"./errors.ts\";\nimport { initialHarnessLifecycleState, reduceHarnessLifecycle } from \"./operation-lifecycle-reducer.ts\";\nimport type {\n\tHarnessAttemptOutcome,\n\tHarnessAttemptRef,\n\tHarnessAttemptSummary,\n\tHarnessLifecycleCommand,\n\tHarnessLifecycleDependencies,\n\tHarnessLifecycleState,\n\tHarnessLifecycleViolation,\n\tHarnessOperationKind,\n\tHarnessOperationOutcome,\n\tHarnessOperationRef,\n} from \"./operation-lifecycle-types.ts\";\n\nexport interface OperationLease {\n\treadonly operation: HarnessOperationRef;\n\treadonly signal: AbortSignal;\n\t/** Resolves once with the recorded outcome when the operation finishes settling. Never rejects. */\n\treadonly settled: Promise<HarnessOperationOutcome>;\n}\n\nexport interface AttemptLease {\n\treadonly attempt: HarnessAttemptRef;\n\t/** The owning operation's signal. Attempts never own a separate AbortController. */\n\treadonly signal: AbortSignal;\n}\n\nexport interface HarnessAbortCapture {\n\treadonly target?: OperationLease;\n\treadonly signalDelivered: boolean;\n}\n\ninterface OperationRecord {\n\treadonly lease: OperationLease;\n\treadonly controller: AbortController;\n\treadonly attemptFinishTimes: Map<string, number>;\n\tsettleStarted: boolean;\n\tresolveSettled: (outcome: HarnessOperationOutcome) => void;\n}\n\n/**\n * Deep-freeze a reducer-produced state in place.\n *\n * Operation and attempt refs are already frozen at creation, and the reducer\n * allocates fresh state objects per transition, so this is O(attempts) per\n * transition rather than a clone. That keeps `getSnapshot()` free to hand out\n * the live reference: `readonly` is compile-time only, but a frozen object\n * resists `Reflect.set` from extension code at runtime too.\n */\nfunction freezeLifecycleState(state: HarnessLifecycleState): HarnessLifecycleState {\n\tif (state.tag !== \"idle\") {\n\t\tfor (const settled of state.attempts) Object.freeze(settled);\n\t\tObject.freeze(state.attempts);\n\t}\n\tif (state.tag === \"settling\") Object.freeze(state.outcome);\n\treturn Object.freeze(state);\n}\n\nexport class OperationLifecycleController {\n\tprivate readonly deps: HarnessLifecycleDependencies;\n\tprivate state: HarnessLifecycleState = freezeLifecycleState(initialHarnessLifecycleState());\n\tprivate current?: OperationRecord;\n\tprivate readonly idleWaiters = new Set<() => void>();\n\n\tconstructor(deps: HarnessLifecycleDependencies) {\n\t\tthis.deps = deps;\n\t}\n\n\tgetSnapshot(): Readonly<HarnessLifecycleState> {\n\t\treturn this.state;\n\t}\n\n\tgetCurrentOperation(): HarnessOperationRef | undefined {\n\t\treturn this.state.tag === \"idle\" ? undefined : this.state.operation;\n\t}\n\n\tbegin(kind: HarnessOperationKind): OperationLease {\n\t\tconst sequence = this.state.tag === \"idle\" ? this.state.lastSequence + 1 : -1;\n\t\t// Frozen once here so every downstream copy — lease, getters, and event\n\t\t// payloads — shares one identity no listener can rewrite.\n\t\tconst operation = createImmutableSnapshot<HarnessOperationRef>({\n\t\t\toperationId: this.deps.createOperationId(),\n\t\t\tsequence,\n\t\t\tkind,\n\t\t\tstartedAtMs: this.deps.now(),\n\t\t});\n\t\tthis.apply({ type: \"begin\", operation });\n\t\tconst controller = new AbortController();\n\t\tlet resolveSettled: (outcome: HarnessOperationOutcome) => void = () => undefined;\n\t\tconst settled = new Promise<HarnessOperationOutcome>((resolve) => {\n\t\t\tresolveSettled = resolve;\n\t\t});\n\t\tconst lease: OperationLease = { operation, signal: controller.signal, settled };\n\t\tthis.current = { lease, controller, attemptFinishTimes: new Map(), settleStarted: false, resolveSettled };\n\t\treturn lease;\n\t}\n\n\tsetStage(lease: OperationLease, stage: Extract<HarnessLifecycleCommand, { type: \"stage\" }>[\"stage\"]): void {\n\t\tthis.expectCurrentLease(lease);\n\t\tthis.apply({ type: \"stage\", operationId: lease.operation.operationId, stage });\n\t}\n\n\tbeginAttempt(lease: OperationLease, reason: HarnessAttemptRef[\"reason\"]): AttemptLease {\n\t\tconst record = this.expectCurrentLease(lease);\n\t\tconst active = this.expectActive();\n\t\tconst attempt = createImmutableSnapshot<HarnessAttemptRef>({\n\t\t\toperationId: lease.operation.operationId,\n\t\t\tattemptId: `${lease.operation.operationId}:a${active.attempts.length}`,\n\t\t\tindex: active.attempts.length,\n\t\t\treason,\n\t\t\tstartedAtMs: this.deps.now(),\n\t\t});\n\t\tthis.apply({ type: \"attempt_begin\", attempt });\n\t\trecord.attemptFinishTimes.delete(attempt.attemptId);\n\t\treturn { attempt, signal: lease.signal };\n\t}\n\n\tfinishAttempt(lease: OperationLease, attempt: AttemptLease, outcome: HarnessAttemptOutcome): void {\n\t\tconst record = this.expectCurrentLease(lease);\n\t\tthis.apply({ type: \"attempt_end\", attemptId: attempt.attempt.attemptId, outcome });\n\t\trecord.attemptFinishTimes.set(attempt.attempt.attemptId, this.deps.now());\n\t}\n\n\t/** Captures the current operation as the abort target; never targets operations started later. */\n\trequestAbort(): HarnessAbortCapture {\n\t\tconst record = this.current;\n\t\tif (record === undefined || this.state.tag === \"idle\") {\n\t\t\treturn { signalDelivered: false };\n\t\t}\n\t\tif (this.state.tag === \"settling\") {\n\t\t\treturn { target: record.lease, signalDelivered: false };\n\t\t}\n\t\tthis.apply({ type: \"abort_request\", operationId: record.lease.operation.operationId });\n\t\tif (!record.controller.signal.aborted) record.controller.abort();\n\t\treturn { target: record.lease, signalDelivered: true };\n\t}\n\n\t/**\n\t * Sole active -> idle path. Runs `finalize` inside the settling barrier, then\n\t * releases state and resolves the lease. A finalizer failure still completes\n\t * the state release and resolves `lease.settled`, then rejects this call.\n\t */\n\tasync settle(\n\t\tlease: OperationLease,\n\t\toutcome: HarnessOperationOutcome,\n\t\tfinalize: () => Promise<void>,\n\t): Promise<HarnessOperationOutcome> {\n\t\tconst record = this.expectCurrentLease(lease);\n\t\tif (record.settleStarted) {\n\t\t\tthrow new AgentHarnessError(\"invalid_state\", `Operation ${lease.operation.operationId} is already settling`);\n\t\t}\n\t\t// A rejected settle_begin (e.g. an attempt still open) is not a settlement.\n\t\t// Marking it as one would wedge the operation: every later settle() would\n\t\t// report \"already settling\" while the state never left `active`, and\n\t\t// `lease.settled` plus every waitForIdle() waiter would hang forever.\n\t\tthis.apply({ type: \"settle_begin\", operationId: lease.operation.operationId, outcome });\n\t\trecord.settleStarted = true;\n\t\tlet finalizeError: unknown;\n\t\ttry {\n\t\t\tawait finalize();\n\t\t} catch (error) {\n\t\t\tfinalizeError = error;\n\t\t}\n\t\tthis.apply({ type: \"settle_finish\", operationId: lease.operation.operationId });\n\t\tthis.current = undefined;\n\t\trecord.resolveSettled(outcome);\n\t\tfor (const release of [...this.idleWaiters]) {\n\t\t\tthis.idleWaiters.delete(release);\n\t\t\trelease();\n\t\t}\n\t\tif (finalizeError !== undefined) throw finalizeError;\n\t\treturn outcome;\n\t}\n\n\t/** Bounded attempt summaries for the current operation; no transcripts. */\n\tgetAttemptSummaries(lease: OperationLease): readonly HarnessAttemptSummary[] {\n\t\tconst record = this.expectCurrentLease(lease);\n\t\tconst attempts = this.state.tag === \"idle\" ? [] : this.state.attempts;\n\t\treturn attempts.map(({ attempt, outcome }) => ({\n\t\t\tattemptId: attempt.attemptId,\n\t\t\tindex: attempt.index,\n\t\t\treason: attempt.reason,\n\t\t\toutcome,\n\t\t\tstartedAtMs: attempt.startedAtMs,\n\t\t\tfinishedAtMs: record.attemptFinishTimes.get(attempt.attemptId) ?? attempt.startedAtMs,\n\t\t}));\n\t}\n\n\t/** The recorded summary for one attempt of the current operation. */\n\tgetAttemptSummary(lease: OperationLease, attempt: AttemptLease): HarnessAttemptSummary {\n\t\tconst attemptId = attempt.attempt.attemptId;\n\t\tconst summary = this.getAttemptSummaries(lease).find((candidate) => candidate.attemptId === attemptId);\n\t\tif (summary === undefined) {\n\t\t\tthrow new AgentHarnessError(\"invalid_state\", `No summary recorded for attempt ${attemptId}`);\n\t\t}\n\t\treturn summary;\n\t}\n\n\tasync waitForIdle(): Promise<void> {\n\t\tif (this.state.tag === \"idle\") return;\n\t\tawait new Promise<void>((resolve) => {\n\t\t\tthis.idleWaiters.add(resolve);\n\t\t});\n\t}\n\n\tprivate expectActive(): Extract<HarnessLifecycleState, { tag: \"active\" }> {\n\t\tif (this.state.tag !== \"active\") {\n\t\t\tthrow new AgentHarnessError(\"invalid_state\", `Expected an active operation, state is ${this.state.tag}`);\n\t\t}\n\t\treturn this.state;\n\t}\n\n\tprivate expectCurrentLease(lease: OperationLease): OperationRecord {\n\t\tconst record = this.current;\n\t\tif (record === undefined || record.lease !== lease) {\n\t\t\tthrow new AgentHarnessError(\n\t\t\t\t\"invalid_state\",\n\t\t\t\t`Stale operation lease for ${lease.operation.operationId}; the operation no longer owns lifecycle state`,\n\t\t\t);\n\t\t}\n\t\treturn record;\n\t}\n\n\tprivate apply(command: HarnessLifecycleCommand): void {\n\t\tconst next = reduceHarnessLifecycle(this.state, command);\n\t\tif (!next.ok) throw this.asHarnessError(next.error);\n\t\tthis.state = freezeLifecycleState(next.value);\n\t}\n\n\tprivate asHarnessError(violation: HarnessLifecycleViolation): AgentHarnessError {\n\t\treturn new AgentHarnessError(violation.code === \"busy\" ? \"busy\" : \"invalid_state\", violation.message, violation);\n\t}\n}\n"]}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owns the single mutable copy of the harness lifecycle state.
|
|
3
|
+
*
|
|
4
|
+
* The controller is the only writer: it builds commands, runs them through
|
|
5
|
+
* the pure reducer, and performs the side effects the reducer is forbidden
|
|
6
|
+
* from touching — clock reads, ID allocation, abort-signal delivery, and
|
|
7
|
+
* settlement of the operation's `settled` promise.
|
|
8
|
+
*
|
|
9
|
+
* Lease discipline: every mutating method requires the `OperationLease`
|
|
10
|
+
* returned by `begin`. A lease that is not the current record fails closed,
|
|
11
|
+
* so a stale async continuation from a finished operation can never mutate
|
|
12
|
+
* the state of a newer one. `settle()` is the only path from `active` to
|
|
13
|
+
* `idle`, runs the caller's finalizer inside the `settling` barrier, and is
|
|
14
|
+
* guarded against double invocation at both controller and reducer level.
|
|
15
|
+
*
|
|
16
|
+
* The `settled` promise resolves with the recorded outcome and never
|
|
17
|
+
* rejects; finalizer failures surface only through the `settle()` rejection,
|
|
18
|
+
* so callers cannot strand an unhandled rejection by ignoring `lease.settled`.
|
|
19
|
+
*/
|
|
20
|
+
import { createImmutableSnapshot } from "../plain-data.js";
|
|
21
|
+
import { AgentHarnessError } from "./errors.js";
|
|
22
|
+
import { initialHarnessLifecycleState, reduceHarnessLifecycle } from "./operation-lifecycle-reducer.js";
|
|
23
|
+
/**
|
|
24
|
+
* Deep-freeze a reducer-produced state in place.
|
|
25
|
+
*
|
|
26
|
+
* Operation and attempt refs are already frozen at creation, and the reducer
|
|
27
|
+
* allocates fresh state objects per transition, so this is O(attempts) per
|
|
28
|
+
* transition rather than a clone. That keeps `getSnapshot()` free to hand out
|
|
29
|
+
* the live reference: `readonly` is compile-time only, but a frozen object
|
|
30
|
+
* resists `Reflect.set` from extension code at runtime too.
|
|
31
|
+
*/
|
|
32
|
+
function freezeLifecycleState(state) {
|
|
33
|
+
if (state.tag !== "idle") {
|
|
34
|
+
for (const settled of state.attempts)
|
|
35
|
+
Object.freeze(settled);
|
|
36
|
+
Object.freeze(state.attempts);
|
|
37
|
+
}
|
|
38
|
+
if (state.tag === "settling")
|
|
39
|
+
Object.freeze(state.outcome);
|
|
40
|
+
return Object.freeze(state);
|
|
41
|
+
}
|
|
42
|
+
export class OperationLifecycleController {
|
|
43
|
+
deps;
|
|
44
|
+
state = freezeLifecycleState(initialHarnessLifecycleState());
|
|
45
|
+
current;
|
|
46
|
+
idleWaiters = new Set();
|
|
47
|
+
constructor(deps) {
|
|
48
|
+
this.deps = deps;
|
|
49
|
+
}
|
|
50
|
+
getSnapshot() {
|
|
51
|
+
return this.state;
|
|
52
|
+
}
|
|
53
|
+
getCurrentOperation() {
|
|
54
|
+
return this.state.tag === "idle" ? undefined : this.state.operation;
|
|
55
|
+
}
|
|
56
|
+
begin(kind) {
|
|
57
|
+
const sequence = this.state.tag === "idle" ? this.state.lastSequence + 1 : -1;
|
|
58
|
+
// Frozen once here so every downstream copy — lease, getters, and event
|
|
59
|
+
// payloads — shares one identity no listener can rewrite.
|
|
60
|
+
const operation = createImmutableSnapshot({
|
|
61
|
+
operationId: this.deps.createOperationId(),
|
|
62
|
+
sequence,
|
|
63
|
+
kind,
|
|
64
|
+
startedAtMs: this.deps.now(),
|
|
65
|
+
});
|
|
66
|
+
this.apply({ type: "begin", operation });
|
|
67
|
+
const controller = new AbortController();
|
|
68
|
+
let resolveSettled = () => undefined;
|
|
69
|
+
const settled = new Promise((resolve) => {
|
|
70
|
+
resolveSettled = resolve;
|
|
71
|
+
});
|
|
72
|
+
const lease = { operation, signal: controller.signal, settled };
|
|
73
|
+
this.current = { lease, controller, attemptFinishTimes: new Map(), settleStarted: false, resolveSettled };
|
|
74
|
+
return lease;
|
|
75
|
+
}
|
|
76
|
+
setStage(lease, stage) {
|
|
77
|
+
this.expectCurrentLease(lease);
|
|
78
|
+
this.apply({ type: "stage", operationId: lease.operation.operationId, stage });
|
|
79
|
+
}
|
|
80
|
+
beginAttempt(lease, reason) {
|
|
81
|
+
const record = this.expectCurrentLease(lease);
|
|
82
|
+
const active = this.expectActive();
|
|
83
|
+
const attempt = createImmutableSnapshot({
|
|
84
|
+
operationId: lease.operation.operationId,
|
|
85
|
+
attemptId: `${lease.operation.operationId}:a${active.attempts.length}`,
|
|
86
|
+
index: active.attempts.length,
|
|
87
|
+
reason,
|
|
88
|
+
startedAtMs: this.deps.now(),
|
|
89
|
+
});
|
|
90
|
+
this.apply({ type: "attempt_begin", attempt });
|
|
91
|
+
record.attemptFinishTimes.delete(attempt.attemptId);
|
|
92
|
+
return { attempt, signal: lease.signal };
|
|
93
|
+
}
|
|
94
|
+
finishAttempt(lease, attempt, outcome) {
|
|
95
|
+
const record = this.expectCurrentLease(lease);
|
|
96
|
+
this.apply({ type: "attempt_end", attemptId: attempt.attempt.attemptId, outcome });
|
|
97
|
+
record.attemptFinishTimes.set(attempt.attempt.attemptId, this.deps.now());
|
|
98
|
+
}
|
|
99
|
+
/** Captures the current operation as the abort target; never targets operations started later. */
|
|
100
|
+
requestAbort() {
|
|
101
|
+
const record = this.current;
|
|
102
|
+
if (record === undefined || this.state.tag === "idle") {
|
|
103
|
+
return { signalDelivered: false };
|
|
104
|
+
}
|
|
105
|
+
if (this.state.tag === "settling") {
|
|
106
|
+
return { target: record.lease, signalDelivered: false };
|
|
107
|
+
}
|
|
108
|
+
this.apply({ type: "abort_request", operationId: record.lease.operation.operationId });
|
|
109
|
+
if (!record.controller.signal.aborted)
|
|
110
|
+
record.controller.abort();
|
|
111
|
+
return { target: record.lease, signalDelivered: true };
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Sole active -> idle path. Runs `finalize` inside the settling barrier, then
|
|
115
|
+
* releases state and resolves the lease. A finalizer failure still completes
|
|
116
|
+
* the state release and resolves `lease.settled`, then rejects this call.
|
|
117
|
+
*/
|
|
118
|
+
async settle(lease, outcome, finalize) {
|
|
119
|
+
const record = this.expectCurrentLease(lease);
|
|
120
|
+
if (record.settleStarted) {
|
|
121
|
+
throw new AgentHarnessError("invalid_state", `Operation ${lease.operation.operationId} is already settling`);
|
|
122
|
+
}
|
|
123
|
+
// A rejected settle_begin (e.g. an attempt still open) is not a settlement.
|
|
124
|
+
// Marking it as one would wedge the operation: every later settle() would
|
|
125
|
+
// report "already settling" while the state never left `active`, and
|
|
126
|
+
// `lease.settled` plus every waitForIdle() waiter would hang forever.
|
|
127
|
+
this.apply({ type: "settle_begin", operationId: lease.operation.operationId, outcome });
|
|
128
|
+
record.settleStarted = true;
|
|
129
|
+
let finalizeError;
|
|
130
|
+
try {
|
|
131
|
+
await finalize();
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
finalizeError = error;
|
|
135
|
+
}
|
|
136
|
+
this.apply({ type: "settle_finish", operationId: lease.operation.operationId });
|
|
137
|
+
this.current = undefined;
|
|
138
|
+
record.resolveSettled(outcome);
|
|
139
|
+
for (const release of [...this.idleWaiters]) {
|
|
140
|
+
this.idleWaiters.delete(release);
|
|
141
|
+
release();
|
|
142
|
+
}
|
|
143
|
+
if (finalizeError !== undefined)
|
|
144
|
+
throw finalizeError;
|
|
145
|
+
return outcome;
|
|
146
|
+
}
|
|
147
|
+
/** Bounded attempt summaries for the current operation; no transcripts. */
|
|
148
|
+
getAttemptSummaries(lease) {
|
|
149
|
+
const record = this.expectCurrentLease(lease);
|
|
150
|
+
const attempts = this.state.tag === "idle" ? [] : this.state.attempts;
|
|
151
|
+
return attempts.map(({ attempt, outcome }) => ({
|
|
152
|
+
attemptId: attempt.attemptId,
|
|
153
|
+
index: attempt.index,
|
|
154
|
+
reason: attempt.reason,
|
|
155
|
+
outcome,
|
|
156
|
+
startedAtMs: attempt.startedAtMs,
|
|
157
|
+
finishedAtMs: record.attemptFinishTimes.get(attempt.attemptId) ?? attempt.startedAtMs,
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
/** The recorded summary for one attempt of the current operation. */
|
|
161
|
+
getAttemptSummary(lease, attempt) {
|
|
162
|
+
const attemptId = attempt.attempt.attemptId;
|
|
163
|
+
const summary = this.getAttemptSummaries(lease).find((candidate) => candidate.attemptId === attemptId);
|
|
164
|
+
if (summary === undefined) {
|
|
165
|
+
throw new AgentHarnessError("invalid_state", `No summary recorded for attempt ${attemptId}`);
|
|
166
|
+
}
|
|
167
|
+
return summary;
|
|
168
|
+
}
|
|
169
|
+
async waitForIdle() {
|
|
170
|
+
if (this.state.tag === "idle")
|
|
171
|
+
return;
|
|
172
|
+
await new Promise((resolve) => {
|
|
173
|
+
this.idleWaiters.add(resolve);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
expectActive() {
|
|
177
|
+
if (this.state.tag !== "active") {
|
|
178
|
+
throw new AgentHarnessError("invalid_state", `Expected an active operation, state is ${this.state.tag}`);
|
|
179
|
+
}
|
|
180
|
+
return this.state;
|
|
181
|
+
}
|
|
182
|
+
expectCurrentLease(lease) {
|
|
183
|
+
const record = this.current;
|
|
184
|
+
if (record === undefined || record.lease !== lease) {
|
|
185
|
+
throw new AgentHarnessError("invalid_state", `Stale operation lease for ${lease.operation.operationId}; the operation no longer owns lifecycle state`);
|
|
186
|
+
}
|
|
187
|
+
return record;
|
|
188
|
+
}
|
|
189
|
+
apply(command) {
|
|
190
|
+
const next = reduceHarnessLifecycle(this.state, command);
|
|
191
|
+
if (!next.ok)
|
|
192
|
+
throw this.asHarnessError(next.error);
|
|
193
|
+
this.state = freezeLifecycleState(next.value);
|
|
194
|
+
}
|
|
195
|
+
asHarnessError(violation) {
|
|
196
|
+
return new AgentHarnessError(violation.code === "busy" ? "busy" : "invalid_state", violation.message, violation);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=operation-lifecycle-controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operation-lifecycle-controller.js","sourceRoot":"","sources":["../../src/harness/operation-lifecycle-controller.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Owns the single mutable copy of the harness lifecycle state.\n *\n * The controller is the only writer: it builds commands, runs them through\n * the pure reducer, and performs the side effects the reducer is forbidden\n * from touching — clock reads, ID allocation, abort-signal delivery, and\n * settlement of the operation's `settled` promise.\n *\n * Lease discipline: every mutating method requires the `OperationLease`\n * returned by `begin`. A lease that is not the current record fails closed,\n * so a stale async continuation from a finished operation can never mutate\n * the state of a newer one. `settle()` is the only path from `active` to\n * `idle`, runs the caller's finalizer inside the `settling` barrier, and is\n * guarded against double invocation at both controller and reducer level.\n *\n * The `settled` promise resolves with the recorded outcome and never\n * rejects; finalizer failures surface only through the `settle()` rejection,\n * so callers cannot strand an unhandled rejection by ignoring `lease.settled`.\n */\n\nimport { createImmutableSnapshot } from \"../plain-data.ts\";\nimport { AgentHarnessError } from \"./errors.ts\";\nimport { initialHarnessLifecycleState, reduceHarnessLifecycle } from \"./operation-lifecycle-reducer.ts\";\nimport type {\n\tHarnessAttemptOutcome,\n\tHarnessAttemptRef,\n\tHarnessAttemptSummary,\n\tHarnessLifecycleCommand,\n\tHarnessLifecycleDependencies,\n\tHarnessLifecycleState,\n\tHarnessLifecycleViolation,\n\tHarnessOperationKind,\n\tHarnessOperationOutcome,\n\tHarnessOperationRef,\n} from \"./operation-lifecycle-types.ts\";\n\nexport interface OperationLease {\n\treadonly operation: HarnessOperationRef;\n\treadonly signal: AbortSignal;\n\t/** Resolves once with the recorded outcome when the operation finishes settling. Never rejects. */\n\treadonly settled: Promise<HarnessOperationOutcome>;\n}\n\nexport interface AttemptLease {\n\treadonly attempt: HarnessAttemptRef;\n\t/** The owning operation's signal. Attempts never own a separate AbortController. */\n\treadonly signal: AbortSignal;\n}\n\nexport interface HarnessAbortCapture {\n\treadonly target?: OperationLease;\n\treadonly signalDelivered: boolean;\n}\n\ninterface OperationRecord {\n\treadonly lease: OperationLease;\n\treadonly controller: AbortController;\n\treadonly attemptFinishTimes: Map<string, number>;\n\tsettleStarted: boolean;\n\tresolveSettled: (outcome: HarnessOperationOutcome) => void;\n}\n\n/**\n * Deep-freeze a reducer-produced state in place.\n *\n * Operation and attempt refs are already frozen at creation, and the reducer\n * allocates fresh state objects per transition, so this is O(attempts) per\n * transition rather than a clone. That keeps `getSnapshot()` free to hand out\n * the live reference: `readonly` is compile-time only, but a frozen object\n * resists `Reflect.set` from extension code at runtime too.\n */\nfunction freezeLifecycleState(state: HarnessLifecycleState): HarnessLifecycleState {\n\tif (state.tag !== \"idle\") {\n\t\tfor (const settled of state.attempts) Object.freeze(settled);\n\t\tObject.freeze(state.attempts);\n\t}\n\tif (state.tag === \"settling\") Object.freeze(state.outcome);\n\treturn Object.freeze(state);\n}\n\nexport class OperationLifecycleController {\n\tprivate readonly deps: HarnessLifecycleDependencies;\n\tprivate state: HarnessLifecycleState = freezeLifecycleState(initialHarnessLifecycleState());\n\tprivate current?: OperationRecord;\n\tprivate readonly idleWaiters = new Set<() => void>();\n\n\tconstructor(deps: HarnessLifecycleDependencies) {\n\t\tthis.deps = deps;\n\t}\n\n\tgetSnapshot(): Readonly<HarnessLifecycleState> {\n\t\treturn this.state;\n\t}\n\n\tgetCurrentOperation(): HarnessOperationRef | undefined {\n\t\treturn this.state.tag === \"idle\" ? undefined : this.state.operation;\n\t}\n\n\tbegin(kind: HarnessOperationKind): OperationLease {\n\t\tconst sequence = this.state.tag === \"idle\" ? this.state.lastSequence + 1 : -1;\n\t\t// Frozen once here so every downstream copy — lease, getters, and event\n\t\t// payloads — shares one identity no listener can rewrite.\n\t\tconst operation = createImmutableSnapshot<HarnessOperationRef>({\n\t\t\toperationId: this.deps.createOperationId(),\n\t\t\tsequence,\n\t\t\tkind,\n\t\t\tstartedAtMs: this.deps.now(),\n\t\t});\n\t\tthis.apply({ type: \"begin\", operation });\n\t\tconst controller = new AbortController();\n\t\tlet resolveSettled: (outcome: HarnessOperationOutcome) => void = () => undefined;\n\t\tconst settled = new Promise<HarnessOperationOutcome>((resolve) => {\n\t\t\tresolveSettled = resolve;\n\t\t});\n\t\tconst lease: OperationLease = { operation, signal: controller.signal, settled };\n\t\tthis.current = { lease, controller, attemptFinishTimes: new Map(), settleStarted: false, resolveSettled };\n\t\treturn lease;\n\t}\n\n\tsetStage(lease: OperationLease, stage: Extract<HarnessLifecycleCommand, { type: \"stage\" }>[\"stage\"]): void {\n\t\tthis.expectCurrentLease(lease);\n\t\tthis.apply({ type: \"stage\", operationId: lease.operation.operationId, stage });\n\t}\n\n\tbeginAttempt(lease: OperationLease, reason: HarnessAttemptRef[\"reason\"]): AttemptLease {\n\t\tconst record = this.expectCurrentLease(lease);\n\t\tconst active = this.expectActive();\n\t\tconst attempt = createImmutableSnapshot<HarnessAttemptRef>({\n\t\t\toperationId: lease.operation.operationId,\n\t\t\tattemptId: `${lease.operation.operationId}:a${active.attempts.length}`,\n\t\t\tindex: active.attempts.length,\n\t\t\treason,\n\t\t\tstartedAtMs: this.deps.now(),\n\t\t});\n\t\tthis.apply({ type: \"attempt_begin\", attempt });\n\t\trecord.attemptFinishTimes.delete(attempt.attemptId);\n\t\treturn { attempt, signal: lease.signal };\n\t}\n\n\tfinishAttempt(lease: OperationLease, attempt: AttemptLease, outcome: HarnessAttemptOutcome): void {\n\t\tconst record = this.expectCurrentLease(lease);\n\t\tthis.apply({ type: \"attempt_end\", attemptId: attempt.attempt.attemptId, outcome });\n\t\trecord.attemptFinishTimes.set(attempt.attempt.attemptId, this.deps.now());\n\t}\n\n\t/** Captures the current operation as the abort target; never targets operations started later. */\n\trequestAbort(): HarnessAbortCapture {\n\t\tconst record = this.current;\n\t\tif (record === undefined || this.state.tag === \"idle\") {\n\t\t\treturn { signalDelivered: false };\n\t\t}\n\t\tif (this.state.tag === \"settling\") {\n\t\t\treturn { target: record.lease, signalDelivered: false };\n\t\t}\n\t\tthis.apply({ type: \"abort_request\", operationId: record.lease.operation.operationId });\n\t\tif (!record.controller.signal.aborted) record.controller.abort();\n\t\treturn { target: record.lease, signalDelivered: true };\n\t}\n\n\t/**\n\t * Sole active -> idle path. Runs `finalize` inside the settling barrier, then\n\t * releases state and resolves the lease. A finalizer failure still completes\n\t * the state release and resolves `lease.settled`, then rejects this call.\n\t */\n\tasync settle(\n\t\tlease: OperationLease,\n\t\toutcome: HarnessOperationOutcome,\n\t\tfinalize: () => Promise<void>,\n\t): Promise<HarnessOperationOutcome> {\n\t\tconst record = this.expectCurrentLease(lease);\n\t\tif (record.settleStarted) {\n\t\t\tthrow new AgentHarnessError(\"invalid_state\", `Operation ${lease.operation.operationId} is already settling`);\n\t\t}\n\t\t// A rejected settle_begin (e.g. an attempt still open) is not a settlement.\n\t\t// Marking it as one would wedge the operation: every later settle() would\n\t\t// report \"already settling\" while the state never left `active`, and\n\t\t// `lease.settled` plus every waitForIdle() waiter would hang forever.\n\t\tthis.apply({ type: \"settle_begin\", operationId: lease.operation.operationId, outcome });\n\t\trecord.settleStarted = true;\n\t\tlet finalizeError: unknown;\n\t\ttry {\n\t\t\tawait finalize();\n\t\t} catch (error) {\n\t\t\tfinalizeError = error;\n\t\t}\n\t\tthis.apply({ type: \"settle_finish\", operationId: lease.operation.operationId });\n\t\tthis.current = undefined;\n\t\trecord.resolveSettled(outcome);\n\t\tfor (const release of [...this.idleWaiters]) {\n\t\t\tthis.idleWaiters.delete(release);\n\t\t\trelease();\n\t\t}\n\t\tif (finalizeError !== undefined) throw finalizeError;\n\t\treturn outcome;\n\t}\n\n\t/** Bounded attempt summaries for the current operation; no transcripts. */\n\tgetAttemptSummaries(lease: OperationLease): readonly HarnessAttemptSummary[] {\n\t\tconst record = this.expectCurrentLease(lease);\n\t\tconst attempts = this.state.tag === \"idle\" ? [] : this.state.attempts;\n\t\treturn attempts.map(({ attempt, outcome }) => ({\n\t\t\tattemptId: attempt.attemptId,\n\t\t\tindex: attempt.index,\n\t\t\treason: attempt.reason,\n\t\t\toutcome,\n\t\t\tstartedAtMs: attempt.startedAtMs,\n\t\t\tfinishedAtMs: record.attemptFinishTimes.get(attempt.attemptId) ?? attempt.startedAtMs,\n\t\t}));\n\t}\n\n\t/** The recorded summary for one attempt of the current operation. */\n\tgetAttemptSummary(lease: OperationLease, attempt: AttemptLease): HarnessAttemptSummary {\n\t\tconst attemptId = attempt.attempt.attemptId;\n\t\tconst summary = this.getAttemptSummaries(lease).find((candidate) => candidate.attemptId === attemptId);\n\t\tif (summary === undefined) {\n\t\t\tthrow new AgentHarnessError(\"invalid_state\", `No summary recorded for attempt ${attemptId}`);\n\t\t}\n\t\treturn summary;\n\t}\n\n\tasync waitForIdle(): Promise<void> {\n\t\tif (this.state.tag === \"idle\") return;\n\t\tawait new Promise<void>((resolve) => {\n\t\t\tthis.idleWaiters.add(resolve);\n\t\t});\n\t}\n\n\tprivate expectActive(): Extract<HarnessLifecycleState, { tag: \"active\" }> {\n\t\tif (this.state.tag !== \"active\") {\n\t\t\tthrow new AgentHarnessError(\"invalid_state\", `Expected an active operation, state is ${this.state.tag}`);\n\t\t}\n\t\treturn this.state;\n\t}\n\n\tprivate expectCurrentLease(lease: OperationLease): OperationRecord {\n\t\tconst record = this.current;\n\t\tif (record === undefined || record.lease !== lease) {\n\t\t\tthrow new AgentHarnessError(\n\t\t\t\t\"invalid_state\",\n\t\t\t\t`Stale operation lease for ${lease.operation.operationId}; the operation no longer owns lifecycle state`,\n\t\t\t);\n\t\t}\n\t\treturn record;\n\t}\n\n\tprivate apply(command: HarnessLifecycleCommand): void {\n\t\tconst next = reduceHarnessLifecycle(this.state, command);\n\t\tif (!next.ok) throw this.asHarnessError(next.error);\n\t\tthis.state = freezeLifecycleState(next.value);\n\t}\n\n\tprivate asHarnessError(violation: HarnessLifecycleViolation): AgentHarnessError {\n\t\treturn new AgentHarnessError(violation.code === \"busy\" ? \"busy\" : \"invalid_state\", violation.message, violation);\n\t}\n}\n"]}
|