@goondan/openharness-base 0.5.7 → 1.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +62 -17
- package/dist/index.js +85 -58
- package/package.json +10 -9
package/dist/index.d.ts
CHANGED
|
@@ -1,27 +1,62 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AgentExtension, LlmChatOptions, Message, ToolDefinition } from '@goondan/openharness-types';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
|
|
4
|
+
* Extension returned by {@link BasicSystemPrompt}. Carries the prompt text so
|
|
5
|
+
* consumers that used to read it off the durable system message (compaction,
|
|
6
|
+
* prewarm, slack.host) can recover it via {@link getSystemPromptText} now that
|
|
7
|
+
* the prompt lives only in the projected view.
|
|
8
|
+
*/
|
|
9
|
+
interface BasicSystemPromptExtension extends AgentExtension {
|
|
10
|
+
readonly systemPromptText: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Recover the system prompt text from a {@link BasicSystemPrompt} extension.
|
|
14
|
+
* Returns `undefined` for any other extension.
|
|
15
|
+
*/
|
|
16
|
+
declare function getSystemPromptText(extension: AgentExtension): string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* BasicSystemPrompt extension — makes a system message lead the prompt on every
|
|
19
|
+
* step.
|
|
9
20
|
*
|
|
10
|
-
*
|
|
21
|
+
* As of 1.0 this is a *projection* via `useModelInput`: the system prompt is
|
|
22
|
+
* part of the throwaway model input, never the durable log. If this projection
|
|
23
|
+
* ran zero times the durable log would still be correct — which is exactly the
|
|
24
|
+
* test for "view, not mutation". A one-time turn middleware (registered with
|
|
25
|
+
* `{ before: "*" }`, so it lands at the outermost band before any step) removes
|
|
26
|
+
* the legacy persisted copy from conversations created by older versions.
|
|
11
27
|
*/
|
|
12
|
-
declare function BasicSystemPrompt(text: string):
|
|
28
|
+
declare function BasicSystemPrompt(text: string): BasicSystemPromptExtension;
|
|
13
29
|
|
|
14
30
|
/**
|
|
15
|
-
* MessageWindow extension —
|
|
16
|
-
*
|
|
31
|
+
* MessageWindow extension — keeps the model input to roughly the most recent
|
|
32
|
+
* `maxMessages` non-system messages.
|
|
33
|
+
*
|
|
34
|
+
* As of 1.0 this is a *projection* via `useModelInput`, not a durable
|
|
35
|
+
* truncation. The old `truncate` event mutated the log and made history
|
|
36
|
+
* unrecoverable; windowing the view instead leaves the durable log intact (pair
|
|
37
|
+
* this with CompactionSummarize if the log itself needs bounding).
|
|
38
|
+
*
|
|
39
|
+
* Two boundary rules keep the windowed view valid:
|
|
40
|
+
* - leading system messages are always retained (the view invariant requires
|
|
41
|
+
* system messages to lead, and the system prompt must survive windowing);
|
|
42
|
+
* - the start boundary is extended backward off any tool-result so a windowed
|
|
43
|
+
* view never begins with an orphaned tool-result whose assistant tool-call
|
|
44
|
+
* was dropped.
|
|
17
45
|
*/
|
|
18
46
|
declare function MessageWindow(config: {
|
|
19
47
|
maxMessages: number;
|
|
20
|
-
}):
|
|
48
|
+
}): AgentExtension;
|
|
21
49
|
|
|
22
50
|
/**
|
|
23
51
|
* CompactionSummarize extension — when message count exceeds `threshold`,
|
|
24
|
-
* removes the oldest messages and replaces them with an
|
|
52
|
+
* removes the oldest *non-system* messages and replaces them with an
|
|
53
|
+
* LLM-generated summary.
|
|
54
|
+
*
|
|
55
|
+
* This is a durable mutation, not a projection: removing history and recording a
|
|
56
|
+
* summary changes the log itself, and must survive replay. It runs as step
|
|
57
|
+
* middleware (`useStep`) so it assembles context before the model call. System
|
|
58
|
+
* messages are never folded into the summary — filtering them out keeps stale
|
|
59
|
+
* prompts/summaries out of the new summary.
|
|
25
60
|
*
|
|
26
61
|
* By default, uses the agent's own LLM (`ctx.llm`) to produce the summary.
|
|
27
62
|
* A custom `summarizer` callback can override this for advanced use cases
|
|
@@ -37,28 +72,38 @@ declare function CompactionSummarize(config: {
|
|
|
37
72
|
/** LLM options for the summarization call (e.g. model override for cheaper summarization). */
|
|
38
73
|
llmOptions?: LlmChatOptions;
|
|
39
74
|
summarizer?: (messages: Message[]) => Promise<string>;
|
|
40
|
-
}):
|
|
75
|
+
}): AgentExtension;
|
|
41
76
|
|
|
42
77
|
/**
|
|
43
78
|
* Logging extension — subscribes to core events and logs them.
|
|
44
79
|
*/
|
|
45
80
|
declare function Logging(config?: {
|
|
46
81
|
logger?: (msg: string) => void;
|
|
47
|
-
}):
|
|
82
|
+
}): AgentExtension;
|
|
48
83
|
|
|
49
84
|
/**
|
|
50
85
|
* ToolSearch extension — registers a meta-tool `search_tools` that searches
|
|
51
86
|
* registered tool names and descriptions by keyword.
|
|
52
87
|
*/
|
|
53
|
-
declare function ToolSearch():
|
|
88
|
+
declare function ToolSearch(): AgentExtension;
|
|
54
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Registration name of {@link RequiredToolsGuard}. Exported as a marker so other
|
|
92
|
+
* middleware can order around it with `before`/`after` without hardcoding the
|
|
93
|
+
* string — e.g. `{ after: REQUIRED_TOOLS_GUARD }`.
|
|
94
|
+
*/
|
|
95
|
+
declare const REQUIRED_TOOLS_GUARD = "required-tools-guard";
|
|
55
96
|
/**
|
|
56
97
|
* RequiredToolsGuard extension — blocks a turn if any required tools are
|
|
57
98
|
* not registered.
|
|
99
|
+
*
|
|
100
|
+
* Registered with `{ after: "*" }` so it sits at the innermost band — the last
|
|
101
|
+
* check before the model call, after all other context-assembling middleware
|
|
102
|
+
* has run.
|
|
58
103
|
*/
|
|
59
104
|
declare function RequiredToolsGuard(config: {
|
|
60
105
|
tools: string[];
|
|
61
|
-
}):
|
|
106
|
+
}): AgentExtension;
|
|
62
107
|
|
|
63
108
|
interface BashToolConfig {
|
|
64
109
|
timeout?: number;
|
|
@@ -81,4 +126,4 @@ interface WaitToolConfig {
|
|
|
81
126
|
}
|
|
82
127
|
declare function WaitTool(config?: WaitToolConfig): ToolDefinition;
|
|
83
128
|
|
|
84
|
-
export { BashTool, type BashToolConfig, BasicSystemPrompt, CompactionSummarize, FileListTool, FileReadTool, FileWriteTool, HttpFetchTool, JsonQueryTool, Logging, MessageWindow, RequiredToolsGuard, TextTransformTool, ToolSearch, WaitTool, type WaitToolConfig };
|
|
129
|
+
export { BashTool, type BashToolConfig, BasicSystemPrompt, type BasicSystemPromptExtension, CompactionSummarize, FileListTool, FileReadTool, FileWriteTool, HttpFetchTool, JsonQueryTool, Logging, MessageWindow, REQUIRED_TOOLS_GUARD, RequiredToolsGuard, TextTransformTool, ToolSearch, WaitTool, type WaitToolConfig, getSystemPromptText };
|
package/dist/index.js
CHANGED
|
@@ -1,59 +1,75 @@
|
|
|
1
1
|
// src/extensions/basic-system-prompt.ts
|
|
2
|
-
|
|
2
|
+
import {
|
|
3
|
+
createMessage
|
|
4
|
+
} from "@goondan/openharness-types";
|
|
5
|
+
var LEGACY_SYSTEM_MESSAGE_ID = "sys-basic-system-prompt";
|
|
6
|
+
var CREATED_BY = "basic-system-prompt";
|
|
7
|
+
function getSystemPromptText(extension) {
|
|
8
|
+
return extension.systemPromptText;
|
|
9
|
+
}
|
|
3
10
|
function BasicSystemPrompt(text) {
|
|
4
11
|
return {
|
|
5
12
|
name: "basic-system-prompt",
|
|
13
|
+
systemPromptText: text,
|
|
6
14
|
register(api) {
|
|
7
|
-
api.
|
|
8
|
-
"turn",
|
|
15
|
+
api.useTurn(
|
|
9
16
|
async (ctx, next) => {
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
type: "appendSystem",
|
|
16
|
-
message: {
|
|
17
|
-
id: SYSTEM_MESSAGE_ID,
|
|
18
|
-
data: {
|
|
19
|
-
role: "system",
|
|
20
|
-
content: text
|
|
21
|
-
},
|
|
22
|
-
metadata: {
|
|
23
|
-
__createdBy: "basic-system-prompt"
|
|
24
|
-
}
|
|
25
|
-
}
|
|
17
|
+
const hasLegacy = ctx.conversation.getMessages().some((m) => m.id === LEGACY_SYSTEM_MESSAGE_ID);
|
|
18
|
+
if (hasLegacy) {
|
|
19
|
+
ctx.conversation.append({
|
|
20
|
+
type: "remove",
|
|
21
|
+
messageId: LEGACY_SYSTEM_MESSAGE_ID
|
|
26
22
|
});
|
|
27
23
|
}
|
|
28
24
|
return next();
|
|
29
25
|
},
|
|
30
|
-
{
|
|
26
|
+
{ before: "*" }
|
|
31
27
|
);
|
|
28
|
+
api.useModelInput((view) => {
|
|
29
|
+
const rest = view.filter((m) => m.id !== LEGACY_SYSTEM_MESSAGE_ID);
|
|
30
|
+
const system = createMessage({
|
|
31
|
+
id: LEGACY_SYSTEM_MESSAGE_ID,
|
|
32
|
+
data: { role: "system", content: text },
|
|
33
|
+
createdBy: CREATED_BY
|
|
34
|
+
});
|
|
35
|
+
return [system, ...rest];
|
|
36
|
+
});
|
|
32
37
|
}
|
|
33
38
|
};
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
// src/extensions/message-window.ts
|
|
42
|
+
function isToolResult(message) {
|
|
43
|
+
if (message.data.role !== "tool") return false;
|
|
44
|
+
const content = message.data.content;
|
|
45
|
+
if (typeof content === "string") return false;
|
|
46
|
+
return content.some(
|
|
47
|
+
(part) => part != null && typeof part === "object" && part.type === "tool-result"
|
|
48
|
+
);
|
|
49
|
+
}
|
|
37
50
|
function MessageWindow(config) {
|
|
38
51
|
return {
|
|
39
52
|
name: "message-window",
|
|
40
53
|
register(api) {
|
|
41
|
-
api.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return next();
|
|
54
|
+
api.useModelInput((view) => {
|
|
55
|
+
const system = view.filter((m) => m.data.role === "system");
|
|
56
|
+
const body = view.filter((m) => m.data.role !== "system");
|
|
57
|
+
if (body.length <= config.maxMessages) return view;
|
|
58
|
+
let start = body.length - config.maxMessages;
|
|
59
|
+
while (start > 0 && isToolResult(body[start])) start--;
|
|
60
|
+
return [...system, ...body.slice(start)];
|
|
49
61
|
});
|
|
50
62
|
}
|
|
51
63
|
};
|
|
52
64
|
}
|
|
53
65
|
|
|
54
66
|
// src/extensions/compaction-summarize.ts
|
|
67
|
+
import {
|
|
68
|
+
createMessage as createMessage2
|
|
69
|
+
} from "@goondan/openharness-types";
|
|
55
70
|
import { randomUUID } from "crypto";
|
|
56
71
|
var DEFAULT_SUMMARY_PROMPT = "You are a conversation compactor. Summarize the following messages into a concise summary that preserves all important context, decisions, facts, and action items. Be thorough but brief. Output only the summary text, nothing else.";
|
|
72
|
+
var CREATED_BY2 = "compaction-summarize";
|
|
57
73
|
function messageToText(m) {
|
|
58
74
|
const role = m.data.role;
|
|
59
75
|
const content = typeof m.data.content === "string" ? m.data.content : JSON.stringify(m.data.content);
|
|
@@ -63,12 +79,14 @@ function CompactionSummarize(config) {
|
|
|
63
79
|
return {
|
|
64
80
|
name: "compaction-summarize",
|
|
65
81
|
register(api) {
|
|
66
|
-
api.
|
|
67
|
-
const messages = ctx.conversation.
|
|
82
|
+
api.useStep(async (ctx, next) => {
|
|
83
|
+
const messages = ctx.conversation.getMessages();
|
|
68
84
|
if (messages.length > config.threshold) {
|
|
69
85
|
const keepCount = Math.floor(config.threshold / 2);
|
|
70
|
-
const
|
|
71
|
-
|
|
86
|
+
const removable = messages.filter((m) => m.data.role !== "system");
|
|
87
|
+
if (removable.length <= keepCount) return next();
|
|
88
|
+
const removeCount = removable.length - keepCount;
|
|
89
|
+
const toRemove = removable.slice(0, removeCount);
|
|
72
90
|
let summaryText;
|
|
73
91
|
if (config.summarizer) {
|
|
74
92
|
summaryText = await config.summarizer([...toRemove]);
|
|
@@ -77,8 +95,16 @@ function CompactionSummarize(config) {
|
|
|
77
95
|
const prompt = config.summaryPrompt ?? DEFAULT_SUMMARY_PROMPT;
|
|
78
96
|
const llmResponse = await ctx.llm.chat(
|
|
79
97
|
[
|
|
80
|
-
|
|
81
|
-
|
|
98
|
+
createMessage2({
|
|
99
|
+
id: `compaction-sys-${randomUUID()}`,
|
|
100
|
+
data: { role: "system", content: prompt },
|
|
101
|
+
createdBy: CREATED_BY2
|
|
102
|
+
}),
|
|
103
|
+
createMessage2({
|
|
104
|
+
id: `compaction-usr-${randomUUID()}`,
|
|
105
|
+
data: { role: "user", content: transcript },
|
|
106
|
+
createdBy: CREATED_BY2
|
|
107
|
+
})
|
|
82
108
|
],
|
|
83
109
|
[],
|
|
84
110
|
// no tools needed for summarization
|
|
@@ -87,26 +113,19 @@ function CompactionSummarize(config) {
|
|
|
87
113
|
);
|
|
88
114
|
summaryText = llmResponse.text ?? transcript;
|
|
89
115
|
}
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
type: "remove",
|
|
93
|
-
messageId: firstToRemove.id
|
|
94
|
-
});
|
|
95
|
-
for (const msg of restToRemove) {
|
|
96
|
-
ctx.conversation.emit({ type: "remove", messageId: msg.id });
|
|
116
|
+
for (const msg of toRemove) {
|
|
117
|
+
ctx.conversation.append({ type: "remove", messageId: msg.id });
|
|
97
118
|
}
|
|
98
|
-
ctx.conversation.
|
|
119
|
+
ctx.conversation.append({
|
|
99
120
|
type: "appendSystem",
|
|
100
|
-
message: {
|
|
121
|
+
message: createMessage2({
|
|
101
122
|
id: `summary-${randomUUID()}`,
|
|
102
123
|
data: {
|
|
103
124
|
role: "system",
|
|
104
125
|
content: `[Summary of earlier conversation]: ${summaryText}`
|
|
105
126
|
},
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
}
|
|
127
|
+
createdBy: CREATED_BY2
|
|
128
|
+
})
|
|
110
129
|
});
|
|
111
130
|
}
|
|
112
131
|
return next();
|
|
@@ -178,20 +197,26 @@ function ToolSearch() {
|
|
|
178
197
|
}
|
|
179
198
|
|
|
180
199
|
// src/extensions/required-tools-guard.ts
|
|
200
|
+
var REQUIRED_TOOLS_GUARD = "required-tools-guard";
|
|
181
201
|
function RequiredToolsGuard(config) {
|
|
182
202
|
return {
|
|
183
|
-
name:
|
|
203
|
+
name: REQUIRED_TOOLS_GUARD,
|
|
184
204
|
register(api) {
|
|
185
|
-
api.
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
`RequiredToolsGuard: missing required tools: ${missing.join(", ")}`
|
|
205
|
+
api.useTurn(
|
|
206
|
+
async (ctx, next) => {
|
|
207
|
+
const registered = api.tools.list().map((t) => t.name);
|
|
208
|
+
const missing = config.tools.filter(
|
|
209
|
+
(name) => !registered.includes(name)
|
|
191
210
|
);
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
211
|
+
if (missing.length > 0) {
|
|
212
|
+
throw new Error(
|
|
213
|
+
`RequiredToolsGuard: missing required tools: ${missing.join(", ")}`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
return next();
|
|
217
|
+
},
|
|
218
|
+
{ after: "*" }
|
|
219
|
+
);
|
|
195
220
|
}
|
|
196
221
|
};
|
|
197
222
|
}
|
|
@@ -530,8 +555,10 @@ export {
|
|
|
530
555
|
JsonQueryTool,
|
|
531
556
|
Logging,
|
|
532
557
|
MessageWindow,
|
|
558
|
+
REQUIRED_TOOLS_GUARD,
|
|
533
559
|
RequiredToolsGuard,
|
|
534
560
|
TextTransformTool,
|
|
535
561
|
ToolSearch,
|
|
536
|
-
WaitTool
|
|
562
|
+
WaitTool,
|
|
563
|
+
getSystemPromptText
|
|
537
564
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goondan/openharness-base",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-rc.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -12,19 +12,20 @@
|
|
|
12
12
|
"files": [
|
|
13
13
|
"dist"
|
|
14
14
|
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsup src/index.ts --format esm --dts",
|
|
17
|
+
"prepack": "pnpm build",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"typecheck": "tsc --noEmit",
|
|
20
|
+
"clean": "rm -rf dist"
|
|
21
|
+
},
|
|
15
22
|
"dependencies": {
|
|
16
|
-
"@goondan/openharness-types": "
|
|
23
|
+
"@goondan/openharness-types": "workspace:^"
|
|
17
24
|
},
|
|
18
25
|
"devDependencies": {
|
|
19
26
|
"@types/node": "^25.5.0",
|
|
20
27
|
"tsup": "^8.4.0",
|
|
21
28
|
"typescript": "^5.7.0",
|
|
22
29
|
"vitest": "^3.0.0"
|
|
23
|
-
},
|
|
24
|
-
"scripts": {
|
|
25
|
-
"build": "tsup src/index.ts --format esm --dts",
|
|
26
|
-
"test": "vitest run",
|
|
27
|
-
"typecheck": "tsc --noEmit",
|
|
28
|
-
"clean": "rm -rf dist"
|
|
29
30
|
}
|
|
30
|
-
}
|
|
31
|
+
}
|