@kuralle-agents/core 0.4.1 → 0.6.0
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/README.md +12 -3
- package/dist/ai-sdk/uiMessageStream.d.ts +51 -0
- package/dist/ai-sdk/uiMessageStream.js +164 -0
- package/dist/events/TurnHandle.js +7 -0
- package/dist/flow/nodeBuilders.d.ts +1 -1
- package/dist/flow/nodeBuilders.js +5 -3
- package/dist/index.d.ts +14 -4
- package/dist/index.js +8 -2
- package/dist/memory/blocks/FilePersistentMemoryStore.d.ts +1 -1
- package/dist/memory/blocks/FilePersistentMemoryStore.js +9 -0
- package/dist/memory/blocks/InMemoryPersistentMemoryStore.d.ts +8 -0
- package/dist/memory/blocks/InMemoryPersistentMemoryStore.js +25 -0
- package/dist/memory/blocks/RoutedPersistentMemoryStore.d.ts +18 -0
- package/dist/memory/blocks/RoutedPersistentMemoryStore.js +35 -0
- package/dist/memory/blocks/TieredPersistentMemoryStore.d.ts +10 -0
- package/dist/memory/blocks/TieredPersistentMemoryStore.js +30 -0
- package/dist/memory/blocks/memoryBlockTool.d.ts +5 -5
- package/dist/memory/blocks/testing.d.ts +11 -0
- package/dist/memory/blocks/testing.js +59 -0
- package/dist/memory/index.d.ts +4 -0
- package/dist/memory/index.js +3 -0
- package/dist/runtime/Runtime.d.ts +3 -0
- package/dist/runtime/Runtime.js +47 -5
- package/dist/runtime/agentReply.js +2 -1
- package/dist/runtime/channels/TextDriver.js +3 -2
- package/dist/runtime/channels/VoiceDriver.js +3 -3
- package/dist/runtime/channels/extractionTurn.js +1 -1
- package/dist/runtime/ctx.d.ts +2 -0
- package/dist/runtime/ctx.js +1 -0
- package/dist/runtime/grounding/defaultStoreRegistry.d.ts +3 -0
- package/dist/runtime/grounding/defaultStoreRegistry.js +10 -0
- package/dist/runtime/grounding/index.d.ts +1 -0
- package/dist/runtime/grounding/index.js +1 -0
- package/dist/runtime/grounding/workingMemory.d.ts +19 -0
- package/dist/runtime/grounding/workingMemory.js +80 -0
- package/dist/runtime/resolveAgentWorkspace.d.ts +10 -0
- package/dist/runtime/resolveAgentWorkspace.js +9 -0
- package/dist/skills/SkillsCapability.d.ts +10 -0
- package/dist/skills/SkillsCapability.js +52 -0
- package/dist/skills/collectSkills.d.ts +17 -0
- package/dist/skills/collectSkills.js +56 -0
- package/dist/skills/index.d.ts +5 -0
- package/dist/skills/index.js +4 -0
- package/dist/skills/inlineSkillStore.d.ts +9 -0
- package/dist/skills/inlineSkillStore.js +37 -0
- package/dist/skills/wireAgentSkills.d.ts +10 -0
- package/dist/skills/wireAgentSkills.js +25 -0
- package/dist/testing/mocks.js +7 -0
- package/dist/tools/effect/defineTool.js +1 -1
- package/dist/tools/effect/index.d.ts +1 -0
- package/dist/tools/effect/index.js +1 -0
- package/dist/tools/effect/wrapAiSdkTool.d.ts +3 -0
- package/dist/tools/effect/wrapAiSdkTool.js +12 -0
- package/dist/tools/fs/createFsTool.d.ts +30 -0
- package/dist/tools/fs/createFsTool.js +200 -0
- package/dist/types/agentConfig.d.ts +16 -3
- package/dist/types/filesystem.d.ts +85 -0
- package/dist/types/filesystem.js +6 -0
- package/dist/types/grounding.d.ts +12 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/run-context.d.ts +11 -2
- package/dist/types/skills.d.ts +19 -0
- package/dist/types/skills.js +1 -0
- package/dist/types/stream.d.ts +3 -0
- package/guides/AGENTS.md +2 -3
- package/guides/FLOWS.md +2 -2
- package/guides/RUNTIME.md +18 -1
- package/guides/TOOLS.md +69 -2
- package/package.json +12 -3
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ const agent = defineAgent({
|
|
|
41
41
|
instructions: 'You are a helpful support agent.',
|
|
42
42
|
model: openai('gpt-4o-mini'),
|
|
43
43
|
tools: buildToolSet({ echo }), // model-visible
|
|
44
|
-
|
|
44
|
+
tools: { echo }, // durable executor
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
const runtime = createRuntime({ agents: [agent], defaultAgentId: 'support' });
|
|
@@ -130,9 +130,18 @@ const runtime = createRuntime({
|
|
|
130
130
|
});
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
-
## HTTP streaming
|
|
133
|
+
## HTTP streaming (web)
|
|
134
134
|
|
|
135
|
-
|
|
135
|
+
For React/web consumers, return a native AI SDK `UIMessageStream` — `useChat` works with no bridge:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
const handle = runtime.run({ input: 'Hello', sessionId: 'demo' });
|
|
139
|
+
return handle.toUIMessageStreamResponse({ sessionId: 'demo' });
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Kuralle orchestration events (flow telemetry, safety blocks, interactive choices) arrive as typed `data-kuralle-*` parts. Import `KuralleUIMessage` and `KuralleDataParts` for compile-time-safe `message.parts` and `useChat({ onData })` handlers.
|
|
143
|
+
|
|
144
|
+
For non-UI consumers (curl, custom transports), use `handle.toResponseStream('sse')` to emit raw `HarnessStreamPart` JSON-SSE. Or use `@kuralle-agents/hono-server` — `POST /api/chat/sse` defaults to native `UIMessageStream`; append `?format=raw` for the legacy wire.
|
|
136
145
|
|
|
137
146
|
## Related
|
|
138
147
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type UIMessage } from 'ai';
|
|
2
|
+
import type { ConversationOutcome } from '../outcomes/types.js';
|
|
3
|
+
import type { ChoiceOption } from '../types/selection.js';
|
|
4
|
+
import type { HarnessStreamPart } from '../types/stream.js';
|
|
5
|
+
export type KuralleMetadata = {
|
|
6
|
+
sessionId?: string;
|
|
7
|
+
};
|
|
8
|
+
export type KuralleDataParts = {
|
|
9
|
+
'kuralle-node': {
|
|
10
|
+
event: 'enter' | 'exit';
|
|
11
|
+
node: string;
|
|
12
|
+
};
|
|
13
|
+
'kuralle-flow': {
|
|
14
|
+
event: 'enter' | 'transition' | 'end';
|
|
15
|
+
flow?: string;
|
|
16
|
+
from?: string;
|
|
17
|
+
to?: string;
|
|
18
|
+
reason?: string;
|
|
19
|
+
};
|
|
20
|
+
'kuralle-handoff': {
|
|
21
|
+
targetAgent: string;
|
|
22
|
+
reason?: string;
|
|
23
|
+
};
|
|
24
|
+
'kuralle-interactive': {
|
|
25
|
+
nodeId: string;
|
|
26
|
+
prompt: string;
|
|
27
|
+
options: ChoiceOption[];
|
|
28
|
+
};
|
|
29
|
+
'kuralle-safety': {
|
|
30
|
+
kind: 'safety-blocked' | 'pipeline-validation-block';
|
|
31
|
+
moderator?: string;
|
|
32
|
+
rationale: string;
|
|
33
|
+
userFacingMessage?: string;
|
|
34
|
+
};
|
|
35
|
+
'kuralle-outcome': {
|
|
36
|
+
outcome: ConversationOutcome;
|
|
37
|
+
};
|
|
38
|
+
'kuralle-control': {
|
|
39
|
+
event: 'interrupted' | 'paused';
|
|
40
|
+
reason?: string;
|
|
41
|
+
waitingFor?: string;
|
|
42
|
+
};
|
|
43
|
+
'kuralle-custom': {
|
|
44
|
+
name: string;
|
|
45
|
+
data: unknown;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
export type KuralleUIMessage = UIMessage<KuralleMetadata, KuralleDataParts>;
|
|
49
|
+
export declare function harnessToUIMessageStream(source: AsyncIterable<HarnessStreamPart>, opts?: {
|
|
50
|
+
sessionId?: string;
|
|
51
|
+
}): ReadableStream;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { createUIMessageStream, generateId, } from 'ai';
|
|
2
|
+
function writeHarnessPart(part, writer) {
|
|
3
|
+
switch (part.type) {
|
|
4
|
+
case 'text-start':
|
|
5
|
+
writer.write({ type: 'text-start', id: part.id });
|
|
6
|
+
break;
|
|
7
|
+
case 'text-delta':
|
|
8
|
+
writer.write({ type: 'text-delta', id: part.id, delta: part.delta });
|
|
9
|
+
break;
|
|
10
|
+
case 'text-end':
|
|
11
|
+
writer.write({ type: 'text-end', id: part.id });
|
|
12
|
+
break;
|
|
13
|
+
case 'text-cancel':
|
|
14
|
+
writer.write({ type: 'text-end', id: part.id });
|
|
15
|
+
break;
|
|
16
|
+
case 'tool-call':
|
|
17
|
+
writer.write({
|
|
18
|
+
type: 'tool-input-available',
|
|
19
|
+
toolCallId: part.toolCallId ?? generateId(),
|
|
20
|
+
toolName: part.toolName,
|
|
21
|
+
input: part.args,
|
|
22
|
+
});
|
|
23
|
+
break;
|
|
24
|
+
case 'tool-result':
|
|
25
|
+
writer.write({
|
|
26
|
+
type: 'tool-output-available',
|
|
27
|
+
toolCallId: part.toolCallId ?? 'unknown',
|
|
28
|
+
output: part.result,
|
|
29
|
+
});
|
|
30
|
+
break;
|
|
31
|
+
case 'node-enter':
|
|
32
|
+
writer.write({
|
|
33
|
+
type: 'data-kuralle-node',
|
|
34
|
+
data: { event: 'enter', node: part.nodeName },
|
|
35
|
+
transient: true,
|
|
36
|
+
});
|
|
37
|
+
break;
|
|
38
|
+
case 'node-exit':
|
|
39
|
+
writer.write({
|
|
40
|
+
type: 'data-kuralle-node',
|
|
41
|
+
data: { event: 'exit', node: part.nodeName },
|
|
42
|
+
transient: true,
|
|
43
|
+
});
|
|
44
|
+
break;
|
|
45
|
+
case 'flow-enter':
|
|
46
|
+
writer.write({
|
|
47
|
+
type: 'data-kuralle-flow',
|
|
48
|
+
data: { event: 'enter', flow: part.flow },
|
|
49
|
+
transient: true,
|
|
50
|
+
});
|
|
51
|
+
break;
|
|
52
|
+
case 'flow-end':
|
|
53
|
+
writer.write({
|
|
54
|
+
type: 'data-kuralle-flow',
|
|
55
|
+
data: { event: 'end', flow: part.flow, reason: part.reason },
|
|
56
|
+
transient: true,
|
|
57
|
+
});
|
|
58
|
+
break;
|
|
59
|
+
case 'flow-transition':
|
|
60
|
+
writer.write({
|
|
61
|
+
type: 'data-kuralle-flow',
|
|
62
|
+
data: { event: 'transition', from: part.from, to: part.to },
|
|
63
|
+
transient: true,
|
|
64
|
+
});
|
|
65
|
+
break;
|
|
66
|
+
case 'handoff':
|
|
67
|
+
writer.write({
|
|
68
|
+
type: 'data-kuralle-handoff',
|
|
69
|
+
id: generateId(),
|
|
70
|
+
data: { targetAgent: part.targetAgent, reason: part.reason },
|
|
71
|
+
});
|
|
72
|
+
break;
|
|
73
|
+
case 'interactive':
|
|
74
|
+
writer.write({
|
|
75
|
+
type: 'data-kuralle-interactive',
|
|
76
|
+
id: part.nodeId,
|
|
77
|
+
data: { nodeId: part.nodeId, prompt: part.prompt, options: part.options },
|
|
78
|
+
});
|
|
79
|
+
break;
|
|
80
|
+
case 'safety-blocked':
|
|
81
|
+
writer.write({
|
|
82
|
+
type: 'data-kuralle-safety',
|
|
83
|
+
id: generateId(),
|
|
84
|
+
data: {
|
|
85
|
+
kind: 'safety-blocked',
|
|
86
|
+
moderator: part.moderator,
|
|
87
|
+
rationale: part.rationale,
|
|
88
|
+
userFacingMessage: part.userFacingMessage,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
break;
|
|
92
|
+
case 'pipeline-validation-block':
|
|
93
|
+
writer.write({
|
|
94
|
+
type: 'data-kuralle-safety',
|
|
95
|
+
id: generateId(),
|
|
96
|
+
data: {
|
|
97
|
+
kind: 'pipeline-validation-block',
|
|
98
|
+
rationale: part.rationale,
|
|
99
|
+
userFacingMessage: part.userFacingMessage,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
break;
|
|
103
|
+
case 'conversation-outcome':
|
|
104
|
+
writer.write({
|
|
105
|
+
type: 'data-kuralle-outcome',
|
|
106
|
+
id: generateId(),
|
|
107
|
+
data: { outcome: part.outcome },
|
|
108
|
+
});
|
|
109
|
+
break;
|
|
110
|
+
case 'interrupted':
|
|
111
|
+
writer.write({
|
|
112
|
+
type: 'data-kuralle-control',
|
|
113
|
+
data: { event: 'interrupted', reason: part.reason },
|
|
114
|
+
transient: true,
|
|
115
|
+
});
|
|
116
|
+
break;
|
|
117
|
+
case 'paused':
|
|
118
|
+
writer.write({
|
|
119
|
+
type: 'data-kuralle-control',
|
|
120
|
+
data: { event: 'paused', waitingFor: part.waitingFor },
|
|
121
|
+
transient: true,
|
|
122
|
+
});
|
|
123
|
+
break;
|
|
124
|
+
case 'custom':
|
|
125
|
+
writer.write({
|
|
126
|
+
type: 'data-kuralle-custom',
|
|
127
|
+
data: { name: part.name, data: part.data },
|
|
128
|
+
transient: true,
|
|
129
|
+
});
|
|
130
|
+
break;
|
|
131
|
+
case 'error':
|
|
132
|
+
throw new Error(part.error);
|
|
133
|
+
case 'done':
|
|
134
|
+
case 'turn-end':
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
export function harnessToUIMessageStream(source, opts) {
|
|
139
|
+
return createUIMessageStream({
|
|
140
|
+
execute: async ({ writer }) => {
|
|
141
|
+
let doneSessionId = opts?.sessionId;
|
|
142
|
+
if (doneSessionId) {
|
|
143
|
+
writer.write({
|
|
144
|
+
type: 'start',
|
|
145
|
+
messageMetadata: { sessionId: doneSessionId },
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
for await (const part of source) {
|
|
149
|
+
if (part.type === 'done' && part.sessionId) {
|
|
150
|
+
doneSessionId = doneSessionId ?? part.sessionId;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
writeHarnessPart(part, writer);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (doneSessionId) {
|
|
157
|
+
writer.write({
|
|
158
|
+
type: 'finish',
|
|
159
|
+
messageMetadata: { sessionId: doneSessionId },
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createUIMessageStreamResponse } from 'ai';
|
|
2
|
+
import { harnessToUIMessageStream } from '../ai-sdk/uiMessageStream.js';
|
|
1
3
|
export function createEventBus() {
|
|
2
4
|
const events = [];
|
|
3
5
|
const waiters = [];
|
|
@@ -48,6 +50,11 @@ export function createTurnHandle(options) {
|
|
|
48
50
|
toResponseStream(format = 'sse') {
|
|
49
51
|
return createResponseStream(bus.events(), format);
|
|
50
52
|
},
|
|
53
|
+
toUIMessageStreamResponse(opts) {
|
|
54
|
+
return createUIMessageStreamResponse({
|
|
55
|
+
stream: harnessToUIMessageStream(bus.events(), opts),
|
|
56
|
+
});
|
|
57
|
+
},
|
|
51
58
|
cancel(reason) {
|
|
52
59
|
abortController.abort(reason);
|
|
53
60
|
bus.close();
|
|
@@ -8,7 +8,7 @@ export declare function buildNodePrompt(node: ReplyNode, state: FlowState): stri
|
|
|
8
8
|
* agent's base instructions (persona / safety / grounding) prefix the node's
|
|
9
9
|
* own instructions. Node instructions layer ON TOP — they never replace the
|
|
10
10
|
* base. Base resolves against the current state so dynamic base prompts work. */
|
|
11
|
-
export declare function composeSystem(base: Instructions | undefined, nodeSystem: string, state: FlowState): string;
|
|
11
|
+
export declare function composeSystem(base: Instructions | undefined, nodeSystem: string, state: FlowState, skillPrompt?: string, workingMemoryPrompt?: string): string;
|
|
12
12
|
export declare function resolveReplyNode(node: ReplyNode, state: FlowState, options?: {
|
|
13
13
|
freeConversation?: boolean;
|
|
14
14
|
}): ResolvedNode;
|
|
@@ -19,9 +19,11 @@ export function buildNodePrompt(node, state) {
|
|
|
19
19
|
* agent's base instructions (persona / safety / grounding) prefix the node's
|
|
20
20
|
* own instructions. Node instructions layer ON TOP — they never replace the
|
|
21
21
|
* base. Base resolves against the current state so dynamic base prompts work. */
|
|
22
|
-
export function composeSystem(base, nodeSystem, state) {
|
|
22
|
+
export function composeSystem(base, nodeSystem, state, skillPrompt, workingMemoryPrompt) {
|
|
23
23
|
const baseText = base ? resolveInstructions(base, state) : '';
|
|
24
|
-
return [baseText, nodeSystem]
|
|
24
|
+
return [baseText, skillPrompt, workingMemoryPrompt, nodeSystem]
|
|
25
|
+
.filter((s) => s && s.trim())
|
|
26
|
+
.join('\n\n');
|
|
25
27
|
}
|
|
26
28
|
function buildNodeTools(node, state) {
|
|
27
29
|
if (!node.tools) {
|
|
@@ -39,7 +41,7 @@ export function resolveReplyNode(node, state, options) {
|
|
|
39
41
|
prompt: buildNodePrompt(node, state),
|
|
40
42
|
tools,
|
|
41
43
|
// Recover the raw executors from the node's `buildToolSet` tools so they run
|
|
42
|
-
// in-flow (with run context) — without also needing `agent.
|
|
44
|
+
// in-flow (with run context) — without also needing `agent.tools`.
|
|
43
45
|
localTools: rawToolsFromSet(tools),
|
|
44
46
|
...(options?.freeConversation && { freeConversation: true }),
|
|
45
47
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -47,8 +47,11 @@ export type { MemoryEntry, SearchMemoryRequest, SearchMemoryResponse, MemoryInge
|
|
|
47
47
|
export { InMemoryMemoryService } from './memory/index.js';
|
|
48
48
|
export { preloadMemoryContext } from './memory/index.js';
|
|
49
49
|
export { extractMemories } from './memory/index.js';
|
|
50
|
-
export type { PersistentMemoryStore, PersistentMemoryBlock, PersistentMemoryConfig, MemoryBlockScope, } from './memory/index.js';
|
|
51
|
-
export { FilePersistentMemoryStore, scanMemoryWrite, buildMemoryBlockTool, DEFAULT_BLOCK_CHAR_LIMIT, DEFAULT_AUTO_LOAD_BLOCKS, } from './memory/index.js';
|
|
50
|
+
export type { PersistentMemoryStore, PersistentMemoryBlock, PersistentMemoryConfig, MemoryBlockScope, WorkingMemoryBlockSpec, WorkingMemoryConfig, } from './memory/index.js';
|
|
51
|
+
export { FilePersistentMemoryStore, InMemoryPersistentMemoryStore, RoutedPersistentMemoryStore, TieredPersistentMemoryStore, scanMemoryWrite, buildMemoryBlockTool, DEFAULT_BLOCK_CHAR_LIMIT, DEFAULT_AUTO_LOAD_BLOCKS, } from './memory/index.js';
|
|
52
|
+
export type { RoutedPersistentMemoryStoreConfig, MemoryRouteFn, } from './memory/index.js';
|
|
53
|
+
export { wireWorkingMemory, loadWorkingMemoryBlocks, formatWorkingMemorySection, resolveWorkingMemoryStore, } from './runtime/grounding/workingMemory.js';
|
|
54
|
+
export { resolveAgentWorkspace, type ResolvedAgentWorkspace, } from './runtime/resolveAgentWorkspace.js';
|
|
52
55
|
export { DEFAULT_CONTEXT_BUDGET, VOICE_CONTEXT_BUDGET, computeMessageHistoryBudget, truncateToTokenBudget, formatMemoryWithBudget, estimateTokenCount, ContextBudget, } from './runtime/ContextBudget.js';
|
|
53
56
|
export type { ContextBudgetConfig } from './runtime/ContextBudget.js';
|
|
54
57
|
export { TokenAccumulator } from './runtime/TokenAccumulator.js';
|
|
@@ -67,16 +70,23 @@ export type { Hooks } from './types/hooks.js';
|
|
|
67
70
|
export type { HarnessHooks } from './types/runtime.js';
|
|
68
71
|
export { defineAgent, defineFlow, reply, collect, action, decide, confirmGate, } from './authoring/index.js';
|
|
69
72
|
export { defineTool } from './types/effectTool.js';
|
|
70
|
-
export {
|
|
73
|
+
export { fsErrorCode } from './types/filesystem.js';
|
|
74
|
+
export { createFsTool } from './tools/fs/createFsTool.js';
|
|
75
|
+
export type { CreateFsToolOptions, GrepHit } from './tools/fs/createFsTool.js';
|
|
76
|
+
export { SkillsCapability, wireAgentSkills, collectRegisteredNames, validateSkillAllowedTools, prepareSkillStore, isSkillStore, InlineSkillStore, } from './skills/index.js';
|
|
77
|
+
export type { WiredAgentSkills, SkillWireAgent } from './skills/index.js';
|
|
78
|
+
export { buildToolSet, toolToAiSdk, wrapAiSdkTool, ToolApprovalDeniedError, ToolTimeoutError, } from './tools/effect/index.js';
|
|
71
79
|
export type { Tool as EffectTool } from './types/effectTool.js';
|
|
72
80
|
export type { AgentRoute } from './types/processors.js';
|
|
73
|
-
export type { AgentConfig, Instructions } from './types/agentConfig.js';
|
|
81
|
+
export type { AgentConfig, AgentWorkspaceConfig, Instructions } from './types/agentConfig.js';
|
|
74
82
|
export type { Flow, FlowNode, Transition, CollectNode, DecideNode, ConfirmGate, NodeGrounding, } from './types/flow.js';
|
|
75
83
|
export { parseConfirmation } from './flow/confirmParse.js';
|
|
76
84
|
export type { ConfirmVerdict } from './flow/confirmParse.js';
|
|
77
85
|
export type { Route } from './types/route.js';
|
|
78
86
|
export type { TurnHandle } from './types/stream.js';
|
|
79
87
|
export type { HarnessStreamPart } from './types/stream.js';
|
|
88
|
+
export { harnessToUIMessageStream } from './ai-sdk/uiMessageStream.js';
|
|
89
|
+
export type { KuralleMetadata, KuralleDataParts, KuralleUIMessage, } from './ai-sdk/uiMessageStream.js';
|
|
80
90
|
export type { ChoiceOption, ResolvedSelection } from './types/selection.js';
|
|
81
91
|
export type { RunState, StepRecord } from './runtime/durable/types.js';
|
|
82
92
|
export type { RunStore } from './runtime/durable/RunStore.js';
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,9 @@ export { getTemplate, registerTemplate, listTemplates, getAllTemplates } from '.
|
|
|
29
29
|
export { InMemoryMemoryService } from './memory/index.js';
|
|
30
30
|
export { preloadMemoryContext } from './memory/index.js';
|
|
31
31
|
export { extractMemories } from './memory/index.js';
|
|
32
|
-
export { FilePersistentMemoryStore, scanMemoryWrite, buildMemoryBlockTool, DEFAULT_BLOCK_CHAR_LIMIT, DEFAULT_AUTO_LOAD_BLOCKS, } from './memory/index.js';
|
|
32
|
+
export { FilePersistentMemoryStore, InMemoryPersistentMemoryStore, RoutedPersistentMemoryStore, TieredPersistentMemoryStore, scanMemoryWrite, buildMemoryBlockTool, DEFAULT_BLOCK_CHAR_LIMIT, DEFAULT_AUTO_LOAD_BLOCKS, } from './memory/index.js';
|
|
33
|
+
export { wireWorkingMemory, loadWorkingMemoryBlocks, formatWorkingMemorySection, resolveWorkingMemoryStore, } from './runtime/grounding/workingMemory.js';
|
|
34
|
+
export { resolveAgentWorkspace, } from './runtime/resolveAgentWorkspace.js';
|
|
33
35
|
export { DEFAULT_CONTEXT_BUDGET, VOICE_CONTEXT_BUDGET, computeMessageHistoryBudget, truncateToTokenBudget, formatMemoryWithBudget, estimateTokenCount, ContextBudget, } from './runtime/ContextBudget.js';
|
|
34
36
|
export { TokenAccumulator } from './runtime/TokenAccumulator.js';
|
|
35
37
|
export { handoffFilters, removeToolHistory, keepRecentMessages, removeKeys, composeFilters, } from './runtime/handoffFilters.js';
|
|
@@ -38,6 +40,10 @@ export { CapabilityHost, TriageCapability, ExtractionCapability, HandoffCapabili
|
|
|
38
40
|
export { filterAuditEntries } from './audit/index.js';
|
|
39
41
|
export { defineAgent, defineFlow, reply, collect, action, decide, confirmGate, } from './authoring/index.js';
|
|
40
42
|
export { defineTool } from './types/effectTool.js';
|
|
41
|
-
export {
|
|
43
|
+
export { fsErrorCode } from './types/filesystem.js';
|
|
44
|
+
export { createFsTool } from './tools/fs/createFsTool.js';
|
|
45
|
+
export { SkillsCapability, wireAgentSkills, collectRegisteredNames, validateSkillAllowedTools, prepareSkillStore, isSkillStore, InlineSkillStore, } from './skills/index.js';
|
|
46
|
+
export { buildToolSet, toolToAiSdk, wrapAiSdkTool, ToolApprovalDeniedError, ToolTimeoutError, } from './tools/effect/index.js';
|
|
42
47
|
export { parseConfirmation } from './flow/confirmParse.js';
|
|
48
|
+
export { harnessToUIMessageStream } from './ai-sdk/uiMessageStream.js';
|
|
43
49
|
export { createRuntime, Runtime, } from './runtime/Runtime.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { PersistentMemoryStore, PersistentMemoryBlock, MemoryBlockScope } from './types.js';
|
|
2
2
|
export interface FilePersistentMemoryStoreOptions {
|
|
3
3
|
/** Root directory. Defaults to `KURALLE_MEMORY_DIR` or `~/.kuralle/memories`. */
|
|
4
4
|
rootDir?: string;
|
|
@@ -9,6 +9,13 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Defaults to `process.env.KURALLE_MEMORY_DIR ?? <homedir>/.kuralle/memories`.
|
|
11
11
|
*
|
|
12
|
+
* Relationship to `AgentConfig.workspace` (RFC-02): this store persists long-term
|
|
13
|
+
* memory *blocks* on the local Node filesystem via `node:fs`. It is not a
|
|
14
|
+
* portable `FileSystem` backend and is not wired into the agent workspace tool.
|
|
15
|
+
* Use `@kuralle-agents/fs` (`InMemoryFs`, `KnowledgeFs`, …) for model-visible
|
|
16
|
+
* workspace exploration; keep `FilePersistentMemoryStore` for durable USER/AGENT
|
|
17
|
+
* markdown blocks when running on Node.
|
|
18
|
+
*
|
|
12
19
|
* Atomicity: writes go to a sibling `.tmp` file then `rename` over the
|
|
13
20
|
* target. `rename` is atomic on POSIX within a single filesystem;
|
|
14
21
|
* Windows is best-effort.
|
|
@@ -21,6 +28,7 @@
|
|
|
21
28
|
import { promises as fs } from 'node:fs';
|
|
22
29
|
import os from 'node:os';
|
|
23
30
|
import path from 'node:path';
|
|
31
|
+
import { registerNodeDefaultWorkingMemoryStore } from '../../runtime/grounding/defaultStoreRegistry.js';
|
|
24
32
|
export class FilePersistentMemoryStore {
|
|
25
33
|
rootDir;
|
|
26
34
|
constructor(opts = {}) {
|
|
@@ -111,3 +119,4 @@ export class FilePersistentMemoryStore {
|
|
|
111
119
|
function isNodeError(err) {
|
|
112
120
|
return typeof err === 'object' && err !== null && 'code' in err;
|
|
113
121
|
}
|
|
122
|
+
registerNodeDefaultWorkingMemoryStore(() => new FilePersistentMemoryStore());
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MemoryBlockScope, PersistentMemoryBlock, PersistentMemoryStore } from './types.js';
|
|
2
|
+
export declare class InMemoryPersistentMemoryStore implements PersistentMemoryStore {
|
|
3
|
+
private readonly blocks;
|
|
4
|
+
loadBlock(scope: MemoryBlockScope, owner: string, key: string): Promise<PersistentMemoryBlock | null>;
|
|
5
|
+
saveBlock(block: PersistentMemoryBlock, owner: string): Promise<void>;
|
|
6
|
+
deleteBlock(scope: MemoryBlockScope, owner: string, key: string): Promise<void>;
|
|
7
|
+
listBlocks(scope: MemoryBlockScope, owner: string): Promise<string[]>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
function blockKey(scope, owner, key) {
|
|
2
|
+
return `${scope}:${owner}:${key}`;
|
|
3
|
+
}
|
|
4
|
+
export class InMemoryPersistentMemoryStore {
|
|
5
|
+
blocks = new Map();
|
|
6
|
+
async loadBlock(scope, owner, key) {
|
|
7
|
+
return this.blocks.get(blockKey(scope, owner, key)) ?? null;
|
|
8
|
+
}
|
|
9
|
+
async saveBlock(block, owner) {
|
|
10
|
+
this.blocks.set(blockKey(block.scope, owner, block.key), {
|
|
11
|
+
...block,
|
|
12
|
+
updatedAt: new Date().toISOString(),
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
async deleteBlock(scope, owner, key) {
|
|
16
|
+
this.blocks.delete(blockKey(scope, owner, key));
|
|
17
|
+
}
|
|
18
|
+
async listBlocks(scope, owner) {
|
|
19
|
+
const prefix = `${scope}:${owner}:`;
|
|
20
|
+
return [...this.blocks.keys()]
|
|
21
|
+
.filter((k) => k.startsWith(prefix))
|
|
22
|
+
.map((k) => k.slice(prefix.length))
|
|
23
|
+
.sort();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { MemoryBlockScope, PersistentMemoryBlock, PersistentMemoryStore } from './types.js';
|
|
2
|
+
export type MemoryRouteFn = (scope: MemoryBlockScope, owner: string) => PersistentMemoryStore;
|
|
3
|
+
export interface RoutedPersistentMemoryStoreConfig {
|
|
4
|
+
routes: Partial<Record<MemoryBlockScope, PersistentMemoryStore>>;
|
|
5
|
+
default?: PersistentMemoryStore;
|
|
6
|
+
route?: MemoryRouteFn;
|
|
7
|
+
}
|
|
8
|
+
export declare class RoutedPersistentMemoryStore implements PersistentMemoryStore {
|
|
9
|
+
private readonly routes;
|
|
10
|
+
private readonly defaultStore?;
|
|
11
|
+
private readonly routeFn?;
|
|
12
|
+
constructor(config: RoutedPersistentMemoryStoreConfig);
|
|
13
|
+
private resolve;
|
|
14
|
+
loadBlock(scope: MemoryBlockScope, owner: string, key: string): Promise<PersistentMemoryBlock | null>;
|
|
15
|
+
saveBlock(block: PersistentMemoryBlock, owner: string): Promise<void>;
|
|
16
|
+
deleteBlock(scope: MemoryBlockScope, owner: string, key: string): Promise<void>;
|
|
17
|
+
listBlocks(scope: MemoryBlockScope, owner: string): Promise<string[]>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export class RoutedPersistentMemoryStore {
|
|
2
|
+
routes;
|
|
3
|
+
defaultStore;
|
|
4
|
+
routeFn;
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.routes = config.routes;
|
|
7
|
+
this.defaultStore = config.default;
|
|
8
|
+
this.routeFn = config.route;
|
|
9
|
+
}
|
|
10
|
+
resolve(scope, owner) {
|
|
11
|
+
if (this.routeFn) {
|
|
12
|
+
return this.routeFn(scope, owner);
|
|
13
|
+
}
|
|
14
|
+
const byScope = this.routes[scope];
|
|
15
|
+
if (byScope) {
|
|
16
|
+
return byScope;
|
|
17
|
+
}
|
|
18
|
+
if (this.defaultStore) {
|
|
19
|
+
return this.defaultStore;
|
|
20
|
+
}
|
|
21
|
+
throw new Error(`No PersistentMemoryStore route for scope=${scope}`);
|
|
22
|
+
}
|
|
23
|
+
async loadBlock(scope, owner, key) {
|
|
24
|
+
return this.resolve(scope, owner).loadBlock(scope, owner, key);
|
|
25
|
+
}
|
|
26
|
+
async saveBlock(block, owner) {
|
|
27
|
+
await this.resolve(block.scope, owner).saveBlock(block, owner);
|
|
28
|
+
}
|
|
29
|
+
async deleteBlock(scope, owner, key) {
|
|
30
|
+
await this.resolve(scope, owner).deleteBlock(scope, owner, key);
|
|
31
|
+
}
|
|
32
|
+
async listBlocks(scope, owner) {
|
|
33
|
+
return this.resolve(scope, owner).listBlocks(scope, owner);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MemoryBlockScope, PersistentMemoryBlock, PersistentMemoryStore } from './types.js';
|
|
2
|
+
export declare class TieredPersistentMemoryStore implements PersistentMemoryStore {
|
|
3
|
+
private readonly cache;
|
|
4
|
+
private readonly durable;
|
|
5
|
+
constructor(cache: PersistentMemoryStore, durable: PersistentMemoryStore);
|
|
6
|
+
loadBlock(scope: MemoryBlockScope, owner: string, key: string): Promise<PersistentMemoryBlock | null>;
|
|
7
|
+
saveBlock(block: PersistentMemoryBlock, owner: string): Promise<void>;
|
|
8
|
+
deleteBlock(scope: MemoryBlockScope, owner: string, key: string): Promise<void>;
|
|
9
|
+
listBlocks(scope: MemoryBlockScope, owner: string): Promise<string[]>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export class TieredPersistentMemoryStore {
|
|
2
|
+
cache;
|
|
3
|
+
durable;
|
|
4
|
+
constructor(cache, durable) {
|
|
5
|
+
this.cache = cache;
|
|
6
|
+
this.durable = durable;
|
|
7
|
+
}
|
|
8
|
+
async loadBlock(scope, owner, key) {
|
|
9
|
+
const cached = await this.cache.loadBlock(scope, owner, key);
|
|
10
|
+
if (cached) {
|
|
11
|
+
return cached;
|
|
12
|
+
}
|
|
13
|
+
const fromDurable = await this.durable.loadBlock(scope, owner, key);
|
|
14
|
+
if (fromDurable) {
|
|
15
|
+
await this.cache.saveBlock(fromDurable, owner);
|
|
16
|
+
}
|
|
17
|
+
return fromDurable;
|
|
18
|
+
}
|
|
19
|
+
async saveBlock(block, owner) {
|
|
20
|
+
await this.durable.saveBlock(block, owner);
|
|
21
|
+
await this.cache.saveBlock(block, owner);
|
|
22
|
+
}
|
|
23
|
+
async deleteBlock(scope, owner, key) {
|
|
24
|
+
await this.durable.deleteBlock(scope, owner, key);
|
|
25
|
+
await this.cache.deleteBlock(scope, owner, key);
|
|
26
|
+
}
|
|
27
|
+
async listBlocks(scope, owner) {
|
|
28
|
+
return this.durable.listBlocks(scope, owner);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -21,7 +21,7 @@ export declare function buildMemoryBlockTool(opts: MemoryBlockToolOptions): impo
|
|
|
21
21
|
empty: boolean;
|
|
22
22
|
updatedAt?: undefined;
|
|
23
23
|
chars?: undefined;
|
|
24
|
-
|
|
24
|
+
error?: undefined;
|
|
25
25
|
message?: undefined;
|
|
26
26
|
ok?: undefined;
|
|
27
27
|
removed?: undefined;
|
|
@@ -38,7 +38,7 @@ export declare function buildMemoryBlockTool(opts: MemoryBlockToolOptions): impo
|
|
|
38
38
|
updatedAt: string | undefined;
|
|
39
39
|
chars: number;
|
|
40
40
|
empty?: undefined;
|
|
41
|
-
|
|
41
|
+
error?: undefined;
|
|
42
42
|
message?: undefined;
|
|
43
43
|
ok?: undefined;
|
|
44
44
|
removed?: undefined;
|
|
@@ -75,7 +75,7 @@ export declare function buildMemoryBlockTool(opts: MemoryBlockToolOptions): impo
|
|
|
75
75
|
empty?: undefined;
|
|
76
76
|
updatedAt?: undefined;
|
|
77
77
|
chars?: undefined;
|
|
78
|
-
|
|
78
|
+
error?: undefined;
|
|
79
79
|
message?: undefined;
|
|
80
80
|
remainingChars?: undefined;
|
|
81
81
|
pattern?: undefined;
|
|
@@ -92,7 +92,7 @@ export declare function buildMemoryBlockTool(opts: MemoryBlockToolOptions): impo
|
|
|
92
92
|
empty?: undefined;
|
|
93
93
|
updatedAt?: undefined;
|
|
94
94
|
chars?: undefined;
|
|
95
|
-
|
|
95
|
+
error?: undefined;
|
|
96
96
|
message?: undefined;
|
|
97
97
|
note?: undefined;
|
|
98
98
|
pattern?: undefined;
|
|
@@ -143,7 +143,7 @@ export declare function buildMemoryBlockTool(opts: MemoryBlockToolOptions): impo
|
|
|
143
143
|
content?: undefined;
|
|
144
144
|
empty?: undefined;
|
|
145
145
|
updatedAt?: undefined;
|
|
146
|
-
|
|
146
|
+
error?: undefined;
|
|
147
147
|
message?: undefined;
|
|
148
148
|
removed?: undefined;
|
|
149
149
|
note?: undefined;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PersistentMemoryStore } from './types.js';
|
|
2
|
+
export type PersistentMemoryStoreFactory = () => PersistentMemoryStore | Promise<PersistentMemoryStore>;
|
|
3
|
+
export type PersistentMemoryDurabilityFactory = () => {
|
|
4
|
+
storeA: PersistentMemoryStore;
|
|
5
|
+
storeB: PersistentMemoryStore;
|
|
6
|
+
} | Promise<{
|
|
7
|
+
storeA: PersistentMemoryStore;
|
|
8
|
+
storeB: PersistentMemoryStore;
|
|
9
|
+
}>;
|
|
10
|
+
export declare function runPersistentMemoryStoreContract(factory: PersistentMemoryStoreFactory): void;
|
|
11
|
+
export declare function runPersistentMemoryDurabilityContract(factory: PersistentMemoryDurabilityFactory): void;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/// <reference types="bun-types" />
|
|
2
|
+
import { describe, test, expect, beforeEach } from 'bun:test';
|
|
3
|
+
const sampleBlock = (overrides = {}) => ({
|
|
4
|
+
key: 'USER',
|
|
5
|
+
scope: 'user',
|
|
6
|
+
content: 'name: Maya\nprefers: vegetarian',
|
|
7
|
+
charLimit: 1000,
|
|
8
|
+
...overrides,
|
|
9
|
+
});
|
|
10
|
+
export function runPersistentMemoryStoreContract(factory) {
|
|
11
|
+
describe('PersistentMemoryStore contract', () => {
|
|
12
|
+
let store;
|
|
13
|
+
beforeEach(async () => {
|
|
14
|
+
store = await factory();
|
|
15
|
+
});
|
|
16
|
+
test('loadBlock returns null for missing block', async () => {
|
|
17
|
+
expect(await store.loadBlock('user', 'alice', 'USER')).toBeNull();
|
|
18
|
+
});
|
|
19
|
+
test('saveBlock + loadBlock round-trips', async () => {
|
|
20
|
+
const block = sampleBlock();
|
|
21
|
+
await store.saveBlock(block, 'maya@example.com');
|
|
22
|
+
const loaded = await store.loadBlock('user', 'maya@example.com', 'USER');
|
|
23
|
+
expect(loaded).not.toBeNull();
|
|
24
|
+
expect(loaded.content).toBe(block.content);
|
|
25
|
+
expect(loaded.scope).toBe('user');
|
|
26
|
+
expect(loaded.key).toBe('USER');
|
|
27
|
+
expect(typeof loaded.updatedAt).toBe('string');
|
|
28
|
+
});
|
|
29
|
+
test('listBlocks returns keys within scope+owner', async () => {
|
|
30
|
+
await store.saveBlock(sampleBlock({ key: 'USER' }), 'bob');
|
|
31
|
+
await store.saveBlock(sampleBlock({ key: 'preferences', content: 'dark mode' }), 'bob');
|
|
32
|
+
await store.saveBlock(sampleBlock({ key: 'MEMORY', scope: 'agent', content: 'notes' }), 'bob');
|
|
33
|
+
expect((await store.listBlocks('user', 'bob')).sort()).toEqual([
|
|
34
|
+
'USER',
|
|
35
|
+
'preferences',
|
|
36
|
+
]);
|
|
37
|
+
expect(await store.listBlocks('agent', 'bob')).toEqual(['MEMORY']);
|
|
38
|
+
});
|
|
39
|
+
test('listBlocks returns empty array when none exist', async () => {
|
|
40
|
+
expect(await store.listBlocks('user', 'never-existed')).toEqual([]);
|
|
41
|
+
});
|
|
42
|
+
test('deleteBlock removes block; no-op when missing', async () => {
|
|
43
|
+
await store.saveBlock(sampleBlock({ key: 'ephemeral' }), 'dave');
|
|
44
|
+
await store.deleteBlock('user', 'dave', 'ephemeral');
|
|
45
|
+
expect(await store.loadBlock('user', 'dave', 'ephemeral')).toBeNull();
|
|
46
|
+
await store.deleteBlock('user', 'dave', 'ephemeral');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
export function runPersistentMemoryDurabilityContract(factory) {
|
|
51
|
+
describe('PersistentMemoryStore durability', () => {
|
|
52
|
+
test('store B reads block written by store A', async () => {
|
|
53
|
+
const { storeA, storeB } = await factory();
|
|
54
|
+
await storeA.saveBlock(sampleBlock({ content: 'durable payload' }), 'owner-1');
|
|
55
|
+
const loaded = await storeB.loadBlock('user', 'owner-1', 'USER');
|
|
56
|
+
expect(loaded?.content).toBe('durable payload');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|