@kuralle-agents/core 0.12.0 → 0.14.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 +89 -4
- package/dist/ai-sdk/uiMessageStream.d.ts +2 -2
- package/dist/ai-sdk/uiMessageStream.js +42 -29
- package/dist/capabilities/LivePromptAssembler.d.ts +1 -1
- package/dist/capabilities/LivePromptAssembler.js +2 -1
- package/dist/capabilities/index.d.ts +1 -1
- package/dist/eval/EvalRunner.js +4 -4
- package/dist/eval/simulation.js +4 -3
- package/dist/events/TurnHandle.d.ts +3 -3
- package/dist/flow/collectDigression.d.ts +5 -2
- package/dist/flow/collectDigression.js +48 -10
- package/dist/flow/collectUntilComplete.js +13 -5
- package/dist/flow/degrade.js +8 -4
- package/dist/flow/emitInteractive.d.ts +2 -2
- package/dist/flow/emitInteractive.js +6 -3
- package/dist/flow/extraction.d.ts +3 -2
- package/dist/flow/extraction.js +14 -4
- package/dist/flow/reduceTransition.d.ts +2 -2
- package/dist/flow/reduceTransition.js +7 -3
- package/dist/flow/runFlow.js +97 -17
- package/dist/foundation/AgentStateController.d.ts +1 -1
- package/dist/foundation/ToolExecutor.d.ts +2 -2
- package/dist/foundation/index.d.ts +0 -11
- package/dist/foundation/index.js +0 -5
- package/dist/index.d.ts +15 -15
- package/dist/index.js +9 -6
- package/dist/outcomes/streamPart.d.ts +2 -2
- package/dist/outcomes/streamPart.js +2 -3
- package/dist/prompts/PromptBuilder.d.ts +3 -3
- package/dist/prompts/PromptBuilder.js +13 -6
- package/dist/prompts/index.d.ts +1 -1
- package/dist/prompts/types.d.ts +9 -2
- package/dist/runtime/InMemoryRetrievalCache.d.ts +25 -0
- package/dist/runtime/InMemoryRetrievalCache.js +59 -0
- package/dist/runtime/KnowledgeProvider.d.ts +3 -2
- package/dist/runtime/KnowledgeProvider.js +60 -31
- package/dist/runtime/Runtime.d.ts +47 -1
- package/dist/runtime/Runtime.js +273 -36
- package/dist/runtime/TokenAccumulator.d.ts +7 -0
- package/dist/runtime/TokenAccumulator.js +7 -0
- package/dist/runtime/TraceRecorder.d.ts +51 -0
- package/dist/runtime/TraceRecorder.js +326 -0
- package/dist/runtime/buildAgentToolSurface.js +1 -1
- package/dist/runtime/channels/TextDriver.js +57 -31
- package/dist/runtime/channels/executeModelTool.d.ts +14 -1
- package/dist/runtime/channels/executeModelTool.js +132 -3
- package/dist/runtime/channels/extractionTurn.js +1 -1
- package/dist/runtime/channels/index.d.ts +0 -3
- package/dist/runtime/channels/index.js +0 -6
- package/dist/runtime/channels/inputBuffer.d.ts +1 -0
- package/dist/runtime/channels/inputBuffer.js +9 -0
- package/dist/runtime/channels/streaming/hostControlSpeak.js +5 -1
- package/dist/runtime/channels/streaming/speakGated.js +24 -12
- package/dist/runtime/citations/index.d.ts +3 -3
- package/dist/runtime/closeRun.js +14 -10
- package/dist/runtime/compaction.d.ts +2 -0
- package/dist/runtime/compaction.js +3 -2
- package/dist/runtime/controlFlowSignal.d.ts +24 -0
- package/dist/runtime/controlFlowSignal.js +29 -0
- package/dist/runtime/ctx.d.ts +3 -4
- package/dist/runtime/ctx.js +147 -60
- package/dist/runtime/durable/RunStore.d.ts +16 -0
- package/dist/runtime/durable/RunStore.js +7 -1
- package/dist/runtime/durable/SessionRunStore.d.ts +7 -2
- package/dist/runtime/durable/SessionRunStore.js +136 -34
- package/dist/runtime/durable/idempotency.d.ts +1 -0
- package/dist/runtime/durable/idempotency.js +3 -0
- package/dist/runtime/durable/replay.js +7 -2
- package/dist/runtime/durable/types.d.ts +11 -0
- package/dist/runtime/goals.d.ts +18 -0
- package/dist/runtime/goals.js +158 -0
- package/dist/runtime/grounding/gather.d.ts +1 -1
- package/dist/runtime/grounding/inMemoryKnowledge.d.ts +1 -1
- package/dist/runtime/grounding/knowledge.d.ts +1 -1
- package/dist/runtime/grounding/knowledge.js +1 -1
- package/dist/runtime/handoffContinuation.d.ts +20 -0
- package/dist/runtime/handoffContinuation.js +30 -0
- package/dist/runtime/handoffOscillation.d.ts +6 -0
- package/dist/runtime/handoffOscillation.js +17 -0
- package/dist/runtime/hostLoop.js +37 -6
- package/dist/runtime/index.d.ts +4 -3
- package/dist/runtime/index.js +2 -1
- package/dist/runtime/openRun.d.ts +2 -0
- package/dist/runtime/openRun.js +46 -17
- package/dist/runtime/outcomeMarking.d.ts +2 -2
- package/dist/runtime/outcomeMarking.js +2 -1
- package/dist/runtime/policies/agentTurn.d.ts +1 -1
- package/dist/runtime/policies/limits.d.ts +1 -0
- package/dist/runtime/policies/limits.js +3 -0
- package/dist/runtime/runHookSafely.d.ts +3 -0
- package/dist/runtime/runHookSafely.js +19 -0
- package/dist/runtime/select.d.ts +5 -1
- package/dist/runtime/select.js +30 -1
- package/dist/runtime/turnTokenUsage.d.ts +28 -0
- package/dist/runtime/turnTokenUsage.js +70 -0
- package/dist/scheduler/index.d.ts +2 -2
- package/dist/scheduler/index.js +1 -1
- package/dist/services/TracingService.js +2 -3
- package/dist/session/SessionStore.d.ts +6 -0
- package/dist/session/SessionStore.js +12 -1
- package/dist/session/stores/MemoryStore.d.ts +1 -1
- package/dist/session/stores/MemoryStore.js +16 -2
- package/dist/session/testing.d.ts +6 -1
- package/dist/session/testing.js +34 -0
- package/dist/session/utils.d.ts +3 -0
- package/dist/session/utils.js +23 -0
- package/dist/skills/collectSkills.d.ts +11 -3
- package/dist/skills/collectSkills.js +51 -8
- package/dist/skills/compositeSkillStore.d.ts +26 -0
- package/dist/skills/compositeSkillStore.js +54 -0
- package/dist/skills/defineSkill.d.ts +21 -0
- package/dist/skills/defineSkill.js +26 -0
- package/dist/skills/fsSkillStore.d.ts +3 -0
- package/dist/skills/fsSkillStore.js +79 -0
- package/dist/skills/index.d.ts +4 -0
- package/dist/skills/index.js +4 -0
- package/dist/skills/parseSkillFrontmatter.d.ts +14 -0
- package/dist/skills/parseSkillFrontmatter.js +183 -0
- package/dist/skills/wireAgentSkills.d.ts +2 -1
- package/dist/skills/wireAgentSkills.js +2 -2
- package/dist/testing/mocks.d.ts +3 -3
- package/dist/testing/mocks.js +7 -3
- package/dist/tools/controlResults.d.ts +14 -0
- package/dist/tools/controlResults.js +15 -0
- package/dist/tools/effect/ToolExecutor.d.ts +6 -0
- package/dist/tools/effect/ToolExecutor.js +75 -36
- package/dist/tools/effect/defineTool.d.ts +11 -0
- package/dist/tools/effect/defineTool.js +3 -0
- package/dist/tools/effect/errors.d.ts +2 -5
- package/dist/tools/effect/errors.js +2 -5
- package/dist/tools/index.d.ts +0 -1
- package/dist/tools/index.js +0 -1
- package/dist/tracing/MemoryTraceStore.d.ts +16 -0
- package/dist/tracing/MemoryTraceStore.js +56 -0
- package/dist/tracing/OtelTraceSink.d.ts +127 -0
- package/dist/tracing/OtelTraceSink.js +101 -0
- package/dist/tracing/TraceStore.d.ts +19 -0
- package/dist/tracing/TraceStore.js +32 -0
- package/dist/tracing/index.d.ts +3 -0
- package/dist/tracing/index.js +3 -0
- package/dist/tracing/testing.d.ts +3 -0
- package/dist/tracing/testing.js +45 -0
- package/dist/types/agentConfig.d.ts +2 -1
- package/dist/types/channel.d.ts +14 -1
- package/dist/types/effectTool.d.ts +6 -0
- package/dist/types/flow.d.ts +1 -1
- package/dist/types/guardrails.d.ts +5 -0
- package/dist/types/hooks.d.ts +2 -2
- package/dist/types/index.d.ts +25 -15
- package/dist/types/index.js +8 -12
- package/dist/types/knowledge.d.ts +213 -0
- package/dist/types/processors.d.ts +0 -58
- package/dist/types/run-context.d.ts +16 -7
- package/dist/types/runtime.d.ts +3 -74
- package/dist/types/session.d.ts +11 -1
- package/dist/types/skills.d.ts +15 -1
- package/dist/types/stream.d.ts +138 -68
- package/dist/types/stream.js +34 -1
- package/dist/types/trace.d.ts +52 -0
- package/guides/AGENTS.md +3 -3
- package/guides/EXAMPLE_VERIFICATION.md +4 -4
- package/guides/FLOWS.md +1 -1
- package/guides/GETTING_STARTED.md +1 -1
- package/guides/RUNTIME.md +20 -15
- package/guides/TOOLS.md +1 -1
- package/package.json +13 -14
- package/dist/foundation/AgentDefinition.d.ts +0 -17
- package/dist/foundation/ConversationEventLog.d.ts +0 -71
- package/dist/foundation/ConversationState.d.ts +0 -33
- package/dist/foundation/ConversationState.js +0 -1
- package/dist/foundation/DefaultConversationEventLog.d.ts +0 -27
- package/dist/foundation/DefaultConversationEventLog.js +0 -205
- package/dist/foundation/DefaultConversationState.d.ts +0 -36
- package/dist/foundation/DefaultConversationState.js +0 -103
- package/dist/foundation/DefaultToolExecutor.d.ts +0 -57
- package/dist/foundation/DefaultToolExecutor.js +0 -132
- package/dist/foundation/createFoundation.d.ts +0 -32
- package/dist/foundation/createFoundation.js +0 -33
- package/dist/hooks/HookRunner.d.ts +0 -37
- package/dist/hooks/HookRunner.js +0 -111
- package/dist/hooks/builtin/logging.d.ts +0 -5
- package/dist/hooks/builtin/logging.js +0 -98
- package/dist/hooks/builtin/metrics.d.ts +0 -7
- package/dist/hooks/builtin/metrics.js +0 -60
- package/dist/hooks/builtin/observability.d.ts +0 -20
- package/dist/hooks/builtin/observability.js +0 -572
- package/dist/hooks/helpers.d.ts +0 -22
- package/dist/hooks/helpers.js +0 -183
- package/dist/hooks/index.d.ts +0 -8
- package/dist/hooks/index.js +0 -6
- package/dist/realtime/RealtimeAudioClient.d.ts +0 -166
- package/dist/realtime/RealtimeAudioClient.js +0 -14
- package/dist/realtime/RealtimeSessionHandle.d.ts +0 -6
- package/dist/realtime/RealtimeSessionHandle.js +0 -1
- package/dist/realtime/index.d.ts +0 -2
- package/dist/realtime/index.js +0 -1
- package/dist/runtime/channels/VoiceDriver.d.ts +0 -31
- package/dist/runtime/channels/VoiceDriver.js +0 -321
- package/dist/runtime/channels/voiceTools.d.ts +0 -9
- package/dist/runtime/channels/voiceTools.js +0 -51
- package/dist/tools/errorHandling.d.ts +0 -35
- package/dist/tools/errorHandling.js +0 -145
- package/dist/types/voice.d.ts +0 -517
- package/dist/types/voice.js +0 -14
- package/dist/utils/isRecord.d.ts +0 -1
- package/dist/utils/isRecord.js +0 -3
- package/dist/utils/messageNormalization.d.ts +0 -2
- package/dist/utils/messageNormalization.js +0 -137
- /package/dist/{foundation/AgentDefinition.js → types/knowledge.js} +0 -0
- /package/dist/{foundation/ConversationEventLog.js → types/trace.js} +0 -0
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type SessionStore } from '../../session/SessionStore.js';
|
|
2
2
|
import type { RunState, StepRecord } from './types.js';
|
|
3
|
-
import { type RunStore } from './RunStore.js';
|
|
3
|
+
import { type RunStore, type StepFinalizePatch } from './RunStore.js';
|
|
4
4
|
export declare class SessionRunStore implements RunStore {
|
|
5
5
|
private readonly sessionStore;
|
|
6
6
|
private readonly sessionId;
|
|
7
|
+
private static readonly CAS_RETRIES;
|
|
7
8
|
constructor(sessionStore: SessionStore, sessionId: string);
|
|
9
|
+
private mutateSession;
|
|
8
10
|
appendStep(runId: string, record: StepRecord): Promise<void>;
|
|
11
|
+
finalizeStep(runId: string, key: string, patch: StepFinalizePatch): Promise<void>;
|
|
12
|
+
reserveSteps(runId: string, count: number): Promise<number[]>;
|
|
9
13
|
getSteps(runId: string): Promise<StepRecord[]>;
|
|
10
14
|
getRunState(runId: string): Promise<RunState | null>;
|
|
11
15
|
putRunState(state: RunState): Promise<void>;
|
|
12
16
|
initRun(state: RunState): Promise<void>;
|
|
17
|
+
pruneStepsBeforeEpoch(runId: string, keepEpoch: number): Promise<void>;
|
|
13
18
|
private requireSession;
|
|
14
19
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { StaleWriteError } from '../../session/SessionStore.js';
|
|
1
2
|
import { DURABLE_RUNS_KEY } from './types.js';
|
|
2
|
-
import { LogConflictError, RunNotFoundError } from './RunStore.js';
|
|
3
|
+
import { LogConflictError, RunNotFoundError, StepNotFoundError, } from './RunStore.js';
|
|
3
4
|
function cloneSession(value) {
|
|
4
5
|
try {
|
|
5
6
|
return structuredClone(value);
|
|
@@ -21,28 +22,115 @@ function getPersistedRun(session, runId) {
|
|
|
21
22
|
export class SessionRunStore {
|
|
22
23
|
sessionStore;
|
|
23
24
|
sessionId;
|
|
25
|
+
static CAS_RETRIES = 8;
|
|
24
26
|
constructor(sessionStore, sessionId) {
|
|
25
27
|
this.sessionStore = sessionStore;
|
|
26
28
|
this.sessionId = sessionId;
|
|
27
29
|
}
|
|
28
|
-
async
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
async mutateSession(mutator) {
|
|
31
|
+
for (let attempt = 0; attempt < SessionRunStore.CAS_RETRIES; attempt++) {
|
|
32
|
+
const session = await this.requireSession();
|
|
33
|
+
await mutator(session);
|
|
34
|
+
try {
|
|
35
|
+
await this.sessionStore.save(session);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (error instanceof StaleWriteError && attempt < SessionRunStore.CAS_RETRIES - 1) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
34
44
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
45
|
+
}
|
|
46
|
+
async appendStep(runId, record) {
|
|
47
|
+
await this.mutateSession((session) => {
|
|
48
|
+
const runs = readRuns(session);
|
|
49
|
+
const persisted = runs[runId];
|
|
50
|
+
if (!persisted) {
|
|
51
|
+
throw new RunNotFoundError(runId);
|
|
52
|
+
}
|
|
53
|
+
const existingAtIndex = persisted.steps[record.index];
|
|
54
|
+
if (existingAtIndex?.name === '__reserve') {
|
|
55
|
+
if (persisted.steps.some((step) => step.key === record.key && step.index !== record.index)) {
|
|
56
|
+
throw new LogConflictError(runId, record.index, persisted.steps.length);
|
|
57
|
+
}
|
|
58
|
+
persisted.steps[record.index] = cloneSession(record);
|
|
59
|
+
}
|
|
60
|
+
else if (persisted.steps.length !== record.index) {
|
|
61
|
+
throw new LogConflictError(runId, record.index, persisted.steps.length);
|
|
62
|
+
}
|
|
63
|
+
else if (persisted.steps.some((step) => step.key === record.key)) {
|
|
64
|
+
throw new LogConflictError(runId, record.index, persisted.steps.length);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
persisted.steps.push(cloneSession(record));
|
|
68
|
+
}
|
|
69
|
+
persisted.runState.updatedAt = Date.now();
|
|
70
|
+
runs[runId] = persisted;
|
|
71
|
+
writeRuns(session, runs);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
async finalizeStep(runId, key, patch) {
|
|
75
|
+
await this.mutateSession((session) => {
|
|
76
|
+
const runs = readRuns(session);
|
|
77
|
+
const persisted = runs[runId];
|
|
78
|
+
if (!persisted) {
|
|
79
|
+
throw new RunNotFoundError(runId);
|
|
80
|
+
}
|
|
81
|
+
const stepIndex = persisted.steps.findIndex((step) => step.key === key);
|
|
82
|
+
if (stepIndex === -1) {
|
|
83
|
+
throw new StepNotFoundError(runId, key);
|
|
84
|
+
}
|
|
85
|
+
const existing = persisted.steps[stepIndex];
|
|
86
|
+
persisted.steps[stepIndex] = cloneSession({
|
|
87
|
+
...existing,
|
|
88
|
+
...patch,
|
|
89
|
+
index: existing.index,
|
|
90
|
+
key: existing.key,
|
|
91
|
+
kind: existing.kind,
|
|
92
|
+
name: existing.name,
|
|
93
|
+
startedAt: existing.startedAt,
|
|
94
|
+
epoch: existing.epoch,
|
|
95
|
+
});
|
|
96
|
+
persisted.runState.updatedAt = Date.now();
|
|
97
|
+
runs[runId] = persisted;
|
|
98
|
+
writeRuns(session, runs);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
async reserveSteps(runId, count) {
|
|
102
|
+
if (count <= 0) {
|
|
103
|
+
return [];
|
|
40
104
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
105
|
+
let indices = [];
|
|
106
|
+
await this.mutateSession((session) => {
|
|
107
|
+
const runs = readRuns(session);
|
|
108
|
+
const persisted = runs[runId];
|
|
109
|
+
if (!persisted) {
|
|
110
|
+
throw new RunNotFoundError(runId);
|
|
111
|
+
}
|
|
112
|
+
const start = persisted.steps.length;
|
|
113
|
+
const now = Date.now();
|
|
114
|
+
const epoch = persisted.runState.runEpoch ?? 0;
|
|
115
|
+
indices = [];
|
|
116
|
+
for (let i = 0; i < count; i++) {
|
|
117
|
+
const index = start + i;
|
|
118
|
+
indices.push(index);
|
|
119
|
+
persisted.steps.push({
|
|
120
|
+
index,
|
|
121
|
+
key: `__reserve:${runId}:${index}`,
|
|
122
|
+
kind: 'tool',
|
|
123
|
+
name: '__reserve',
|
|
124
|
+
status: 'running',
|
|
125
|
+
startedAt: now,
|
|
126
|
+
epoch,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
persisted.runState.updatedAt = now;
|
|
130
|
+
runs[runId] = persisted;
|
|
131
|
+
writeRuns(session, runs);
|
|
132
|
+
});
|
|
133
|
+
return indices;
|
|
46
134
|
}
|
|
47
135
|
async getSteps(runId) {
|
|
48
136
|
const session = await this.requireSession();
|
|
@@ -58,25 +146,39 @@ export class SessionRunStore {
|
|
|
58
146
|
return persisted ? cloneSession(persisted.runState) : null;
|
|
59
147
|
}
|
|
60
148
|
async putRunState(state) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
149
|
+
await this.mutateSession((session) => {
|
|
150
|
+
const runs = readRuns(session);
|
|
151
|
+
const existing = runs[state.runId];
|
|
152
|
+
runs[state.runId] = {
|
|
153
|
+
runState: cloneSession({ ...state, updatedAt: Date.now() }),
|
|
154
|
+
steps: existing?.steps.map((step) => cloneSession(step)) ?? [],
|
|
155
|
+
};
|
|
156
|
+
writeRuns(session, runs);
|
|
157
|
+
});
|
|
70
158
|
}
|
|
71
159
|
async initRun(state) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
160
|
+
await this.mutateSession((session) => {
|
|
161
|
+
const runs = readRuns(session);
|
|
162
|
+
runs[state.runId] = {
|
|
163
|
+
runState: cloneSession(state),
|
|
164
|
+
steps: [],
|
|
165
|
+
};
|
|
166
|
+
writeRuns(session, runs);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
async pruneStepsBeforeEpoch(runId, keepEpoch) {
|
|
170
|
+
await this.mutateSession((session) => {
|
|
171
|
+
const runs = readRuns(session);
|
|
172
|
+
const persisted = runs[runId];
|
|
173
|
+
if (!persisted) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const kept = persisted.steps.filter((step) => step.epoch === undefined || step.epoch >= keepEpoch);
|
|
177
|
+
persisted.steps = kept.map((step, index) => ({ ...cloneSession(step), index }));
|
|
178
|
+
persisted.runState.updatedAt = Date.now();
|
|
179
|
+
runs[runId] = persisted;
|
|
180
|
+
writeRuns(session, runs);
|
|
181
|
+
});
|
|
80
182
|
}
|
|
81
183
|
async requireSession() {
|
|
82
184
|
const session = await this.sessionStore.get(this.sessionId);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare function logicalRunId(runId: string, runEpoch: number | undefined): string;
|
|
1
2
|
export declare function idempotencyKey(runId: string, callsite: string, payload: unknown): string;
|
|
2
3
|
export declare function toolEffectKey(runId: string, callsite: string, name: string, args: unknown): string;
|
|
3
4
|
export declare function pauseEffectKey(runId: string, callsite: string, name: string): string;
|
|
@@ -12,6 +12,9 @@ function stableStringify(value) {
|
|
|
12
12
|
return val;
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
|
+
export function logicalRunId(runId, runEpoch) {
|
|
16
|
+
return `${runId}#${runEpoch ?? 0}`;
|
|
17
|
+
}
|
|
15
18
|
export function idempotencyKey(runId, callsite, payload) {
|
|
16
19
|
const material = stableStringify({ runId, callsite, payload });
|
|
17
20
|
return createHash('sha256').update(material).digest('hex');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { pauseEffectKey } from './idempotency.js';
|
|
1
|
+
import { pauseEffectKey, logicalRunId } from './idempotency.js';
|
|
2
2
|
export function findStepByKey(steps, key) {
|
|
3
3
|
return steps.find((step) => step.key === key);
|
|
4
4
|
}
|
|
@@ -14,7 +14,10 @@ export async function recordSignalDelivery(runStore, runState, delivery) {
|
|
|
14
14
|
if (!waitingFor || waitingFor.signalName !== delivery.name) {
|
|
15
15
|
throw new Error(`Signal ${delivery.name} does not match waitingFor ${waitingFor?.signalName ?? 'none'}`);
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
// Must match the key pauseEffect used when it suspended: the keystone scopes the
|
|
18
|
+
// effect-key namespace by logical run (runId#epoch), so the resume-side delivery
|
|
19
|
+
// key has to use logicalRunId too, else the pause never finds its own decision.
|
|
20
|
+
const key = pauseEffectKey(logicalRunId(runState.runId, runState.runEpoch), waitingFor.callsite, delivery.name);
|
|
18
21
|
if (findStepByKey(steps, key)) {
|
|
19
22
|
return false;
|
|
20
23
|
}
|
|
@@ -25,9 +28,11 @@ export async function recordSignalDelivery(runStore, runState, delivery) {
|
|
|
25
28
|
kind: waitingFor.approval ? 'approval' : 'signal',
|
|
26
29
|
name: delivery.name,
|
|
27
30
|
signalId: delivery.signalId,
|
|
31
|
+
status: 'finished',
|
|
28
32
|
result: delivery.payload,
|
|
29
33
|
startedAt: now,
|
|
30
34
|
finishedAt: now,
|
|
35
|
+
epoch: runState.runEpoch ?? 0,
|
|
31
36
|
};
|
|
32
37
|
await runStore.appendStep(runState.runId, record);
|
|
33
38
|
runState.waitingFor = undefined;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ModelMessage } from 'ai';
|
|
2
2
|
export type StepKind = 'tool' | 'approval' | 'signal' | 'now' | 'uuid';
|
|
3
|
+
export type StepStatus = 'running' | 'paused' | 'finished' | 'error' | 'aborted';
|
|
3
4
|
export interface StepRecord {
|
|
4
5
|
index: number;
|
|
5
6
|
key: string;
|
|
@@ -11,8 +12,12 @@ export interface StepRecord {
|
|
|
11
12
|
name: string;
|
|
12
13
|
message: string;
|
|
13
14
|
};
|
|
15
|
+
/** Intent lifecycle: `running` = pending execute; `finished`/`error` = finalized. Legacy steps omit status. */
|
|
16
|
+
status?: StepStatus;
|
|
14
17
|
startedAt: number;
|
|
15
18
|
finishedAt?: number;
|
|
19
|
+
/** Logical-run epoch when this step was recorded. Absent on legacy steps → prune keeps them until superseded. */
|
|
20
|
+
epoch?: number;
|
|
16
21
|
}
|
|
17
22
|
interface WaitingFor {
|
|
18
23
|
signalName: string;
|
|
@@ -36,6 +41,12 @@ export interface RunState {
|
|
|
36
41
|
messages: ModelMessage[];
|
|
37
42
|
createdAt: number;
|
|
38
43
|
updatedAt: number;
|
|
44
|
+
/** Monotonic logical-run counter. Increments on each fresh turn; stable across suspend/resume.
|
|
45
|
+
* Scopes the durable effect-key namespace so a new turn re-executes rather than replaying a
|
|
46
|
+
* prior turn's cached result (F6/G8). Absent on legacy runs → treat as 0. */
|
|
47
|
+
runEpoch?: number;
|
|
48
|
+
/** Inbound message idempotency keys already accepted (H2 webhook-retry dedup). */
|
|
49
|
+
processedInboundKeys?: string[];
|
|
39
50
|
}
|
|
40
51
|
export interface SignalDelivery {
|
|
41
52
|
signalId: string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { LanguageModel } from 'ai';
|
|
2
|
+
import type { RunContext } from '../types/run-context.js';
|
|
3
|
+
export declare const GOALS_KEY = "__goals";
|
|
4
|
+
export type GoalStatus = 'open' | 'resolved';
|
|
5
|
+
export interface TrackedGoal {
|
|
6
|
+
topic: string;
|
|
7
|
+
status: GoalStatus;
|
|
8
|
+
lastTurn: number;
|
|
9
|
+
note?: string;
|
|
10
|
+
}
|
|
11
|
+
export type GoalsState = Record<string, unknown>;
|
|
12
|
+
export declare function getGoals(state: GoalsState): TrackedGoal[];
|
|
13
|
+
export declare function addGoal(state: GoalsState, topic: string, lastTurn?: number, note?: string): GoalsState;
|
|
14
|
+
export declare function resolveGoal(state: GoalsState, topic: string, lastTurn?: number): GoalsState;
|
|
15
|
+
export declare function listOpenGoals(state: GoalsState): string[];
|
|
16
|
+
export declare function projectGoalsPrompt(goals: TrackedGoal[]): string;
|
|
17
|
+
export declare function projectGoalsPromptFromState(state: GoalsState): string;
|
|
18
|
+
export declare function updateGoalsFromTurn(ctx: RunContext, model: LanguageModel): Promise<void>;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { generateObject } from 'ai';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export const GOALS_KEY = '__goals';
|
|
4
|
+
const goalPatchSchema = z.object({
|
|
5
|
+
add: z
|
|
6
|
+
.array(z.object({
|
|
7
|
+
topic: z.string(),
|
|
8
|
+
note: z.union([z.string(), z.null()]),
|
|
9
|
+
}))
|
|
10
|
+
.default([]),
|
|
11
|
+
resolve: z.array(z.string()).default([]),
|
|
12
|
+
});
|
|
13
|
+
function isTrackedGoal(value) {
|
|
14
|
+
if (!value || typeof value !== 'object') {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const goal = value;
|
|
18
|
+
return (typeof goal.topic === 'string' &&
|
|
19
|
+
(goal.status === 'open' || goal.status === 'resolved') &&
|
|
20
|
+
typeof goal.lastTurn === 'number');
|
|
21
|
+
}
|
|
22
|
+
function normalizeTopic(topic) {
|
|
23
|
+
return topic.trim().toLowerCase();
|
|
24
|
+
}
|
|
25
|
+
export function getGoals(state) {
|
|
26
|
+
const raw = state[GOALS_KEY];
|
|
27
|
+
if (!Array.isArray(raw)) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
return raw.filter(isTrackedGoal);
|
|
31
|
+
}
|
|
32
|
+
export function addGoal(state, topic, lastTurn = 0, note) {
|
|
33
|
+
const trimmed = topic.trim();
|
|
34
|
+
if (!trimmed) {
|
|
35
|
+
return state;
|
|
36
|
+
}
|
|
37
|
+
const goals = getGoals(state);
|
|
38
|
+
const key = normalizeTopic(trimmed);
|
|
39
|
+
const existingIndex = goals.findIndex((goal) => normalizeTopic(goal.topic) === key);
|
|
40
|
+
if (existingIndex >= 0) {
|
|
41
|
+
const next = [...goals];
|
|
42
|
+
const existing = next[existingIndex];
|
|
43
|
+
next[existingIndex] = {
|
|
44
|
+
...existing,
|
|
45
|
+
topic: trimmed,
|
|
46
|
+
status: 'open',
|
|
47
|
+
lastTurn,
|
|
48
|
+
note: note ?? existing.note,
|
|
49
|
+
};
|
|
50
|
+
return { ...state, [GOALS_KEY]: next };
|
|
51
|
+
}
|
|
52
|
+
const entry = { topic: trimmed, status: 'open', lastTurn };
|
|
53
|
+
if (note) {
|
|
54
|
+
entry.note = note;
|
|
55
|
+
}
|
|
56
|
+
return { ...state, [GOALS_KEY]: [...goals, entry] };
|
|
57
|
+
}
|
|
58
|
+
export function resolveGoal(state, topic, lastTurn = 0) {
|
|
59
|
+
const trimmed = topic.trim();
|
|
60
|
+
if (!trimmed) {
|
|
61
|
+
return state;
|
|
62
|
+
}
|
|
63
|
+
const key = normalizeTopic(trimmed);
|
|
64
|
+
const goals = getGoals(state);
|
|
65
|
+
let changed = false;
|
|
66
|
+
const next = goals.map((goal) => {
|
|
67
|
+
if (normalizeTopic(goal.topic) !== key) {
|
|
68
|
+
return goal;
|
|
69
|
+
}
|
|
70
|
+
changed = true;
|
|
71
|
+
return { ...goal, status: 'resolved', lastTurn };
|
|
72
|
+
});
|
|
73
|
+
if (!changed) {
|
|
74
|
+
return state;
|
|
75
|
+
}
|
|
76
|
+
return { ...state, [GOALS_KEY]: next };
|
|
77
|
+
}
|
|
78
|
+
export function listOpenGoals(state) {
|
|
79
|
+
return getGoals(state)
|
|
80
|
+
.filter((goal) => goal.status === 'open')
|
|
81
|
+
.map((goal) => goal.topic);
|
|
82
|
+
}
|
|
83
|
+
export function projectGoalsPrompt(goals) {
|
|
84
|
+
const open = goals.filter((goal) => goal.status === 'open');
|
|
85
|
+
if (open.length === 0) {
|
|
86
|
+
return '';
|
|
87
|
+
}
|
|
88
|
+
const parts = open.map((goal) => goal.note ? `${goal.topic} (${goal.status}, ${goal.note})` : `${goal.topic} (${goal.status})`);
|
|
89
|
+
return `Open threads: ${parts.join('; ')}`;
|
|
90
|
+
}
|
|
91
|
+
export function projectGoalsPromptFromState(state) {
|
|
92
|
+
return projectGoalsPrompt(getGoals(state));
|
|
93
|
+
}
|
|
94
|
+
function readSessionTurn(state) {
|
|
95
|
+
const value = state['__ariaSessionTurn'];
|
|
96
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
97
|
+
}
|
|
98
|
+
function latestExchange(messages) {
|
|
99
|
+
let assistant = '';
|
|
100
|
+
let user = '';
|
|
101
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
102
|
+
const message = messages[index];
|
|
103
|
+
if (!assistant && message?.role === 'assistant' && typeof message.content === 'string') {
|
|
104
|
+
assistant = message.content;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (assistant && message?.role === 'user' && typeof message.content === 'string') {
|
|
108
|
+
user = message.content;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (!user || !assistant) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
return { user, assistant };
|
|
116
|
+
}
|
|
117
|
+
function applyGoalPatch(state, patch) {
|
|
118
|
+
let next = state;
|
|
119
|
+
const turn = readSessionTurn(state);
|
|
120
|
+
for (const topic of patch.resolve) {
|
|
121
|
+
next = resolveGoal(next, topic, turn);
|
|
122
|
+
}
|
|
123
|
+
for (const entry of patch.add) {
|
|
124
|
+
const note = entry.note ?? undefined;
|
|
125
|
+
next = addGoal(next, entry.topic, turn, note);
|
|
126
|
+
}
|
|
127
|
+
return next;
|
|
128
|
+
}
|
|
129
|
+
export async function updateGoalsFromTurn(ctx, model) {
|
|
130
|
+
const exchange = latestExchange(ctx.runState.messages);
|
|
131
|
+
if (!exchange) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const currentGoals = getGoals(ctx.session.workingMemory);
|
|
135
|
+
const goalsSummary = currentGoals.length === 0
|
|
136
|
+
? 'none'
|
|
137
|
+
: currentGoals
|
|
138
|
+
.map((goal) => goal.note
|
|
139
|
+
? `${goal.topic} [${goal.status}] — ${goal.note}`
|
|
140
|
+
: `${goal.topic} [${goal.status}]`)
|
|
141
|
+
.join('\n');
|
|
142
|
+
const { object } = await generateObject({
|
|
143
|
+
model,
|
|
144
|
+
schema: goalPatchSchema,
|
|
145
|
+
temperature: 0,
|
|
146
|
+
system: 'You track conversational threads (goals/topics the user may circle back to). ' +
|
|
147
|
+
'Given the latest exchange and current tracked threads, return only schema fields. ' +
|
|
148
|
+
'Add a topic when the user opens a new thread or revisits one not yet tracked. ' +
|
|
149
|
+
'Resolve a topic when the exchange clearly completes or abandons it. ' +
|
|
150
|
+
'Keep topics short labels (1-4 words). Do not invent threads unrelated to the exchange.',
|
|
151
|
+
prompt: `Current tracked threads:\n${goalsSummary}\n\n` +
|
|
152
|
+
`Latest user message:\n${exchange.user}\n\n` +
|
|
153
|
+
`Latest assistant message:\n${exchange.assistant}\n\n` +
|
|
154
|
+
'Return add[] for new/reopened open topics and resolve[] for completed topics.',
|
|
155
|
+
});
|
|
156
|
+
const patched = applyGoalPatch(ctx.session.workingMemory, object);
|
|
157
|
+
ctx.session.workingMemory = patched;
|
|
158
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SourceRef } from '../../types/
|
|
1
|
+
import type { SourceRef } from '../../types/knowledge.js';
|
|
2
2
|
import type { GatherScope, RunContext } from '../../types/run-context.js';
|
|
3
3
|
export type { GatherScope } from '../../types/run-context.js';
|
|
4
4
|
export interface GatherResult {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { KnowledgeProviderConfig, KnowledgeRetrieverAdapter } from '../../types/
|
|
1
|
+
import type { KnowledgeProviderConfig, KnowledgeRetrieverAdapter } from '../../types/knowledge.js';
|
|
2
2
|
export interface InMemoryKnowledgeDocument {
|
|
3
3
|
id?: string;
|
|
4
4
|
text: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AgentConfig } from '../../types/agentConfig.js';
|
|
2
2
|
import type { AutoRetrieveProvider } from '../../types/run-context.js';
|
|
3
3
|
import type { AnyTool } from '../../types/effectTool.js';
|
|
4
|
-
import type { KnowledgeProviderConfig } from '../../types/
|
|
4
|
+
import type { KnowledgeProviderConfig } from '../../types/knowledge.js';
|
|
5
5
|
import { KnowledgeProvider } from '../KnowledgeProvider.js';
|
|
6
6
|
export declare function buildKnowledgeProvider(config: KnowledgeProviderConfig): KnowledgeProvider;
|
|
7
7
|
export declare function buildAutoRetrieveProvider(provider: KnowledgeProvider, agent: AgentConfig): AutoRetrieveProvider | undefined;
|
|
@@ -42,7 +42,7 @@ export function buildAutoRetrieveProvider(provider, agent) {
|
|
|
42
42
|
retrieve: async (ctx, scope) => {
|
|
43
43
|
const query = scope?.query ?? latestUserMessage(ctx);
|
|
44
44
|
const merged = scope?.knowledge ? { ...overrides, ...scope.knowledge } : overrides;
|
|
45
|
-
const cache =
|
|
45
|
+
const cache = ctx.retrievalCache;
|
|
46
46
|
const { results, events } = await provider.retrieve(query || ' ', cache, merged, false);
|
|
47
47
|
for (const event of events) {
|
|
48
48
|
ctx.emit(event);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Silent handoff: a transfer between agents should read as one continuous
|
|
3
|
+
* assistant to the user. The transfer itself is already a silent control tool
|
|
4
|
+
* call, but the TARGET agent's own instructions may tell it to introduce itself
|
|
5
|
+
* ("Bill here"), which leaks the switch. On a silent handoff we append a strong
|
|
6
|
+
* continuation directive to the target's system prompt so it does not greet or
|
|
7
|
+
* re-introduce — mirroring the OpenAI Agents SDK's `nest_handoff_history`
|
|
8
|
+
* context-note approach. Off (visible handoff) leaves the target's instructions
|
|
9
|
+
* untouched.
|
|
10
|
+
*/
|
|
11
|
+
import type { Instructions } from '../types/agentConfig.js';
|
|
12
|
+
export declare const HANDOFF_CONTINUATION_DIRECTIVE: string;
|
|
13
|
+
/**
|
|
14
|
+
* Append the continuation directive to a target agent's instructions on a silent
|
|
15
|
+
* handoff. Handles every `Instructions` form: a string is suffixed; a (sync)
|
|
16
|
+
* function is wrapped so the directive is appended to its resolved text; an
|
|
17
|
+
* `AgentPrompt` object is left untouched (it cannot be safely suffixed, and is
|
|
18
|
+
* rare as a base layer); `undefined` yields the directive alone.
|
|
19
|
+
*/
|
|
20
|
+
export declare function applyHandoffContinuation(base: Instructions | undefined): Instructions;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const HANDOFF_CONTINUATION_DIRECTIVE = 'You are seamlessly continuing an ongoing conversation with the same user — this is ' +
|
|
2
|
+
'not a new conversation. Do NOT greet the user, introduce yourself, state your name ' +
|
|
3
|
+
'or role, say "hello", or mention any transfer, connection, or that a different ' +
|
|
4
|
+
'assistant is now helping. Ignore any earlier instruction to open with a greeting or ' +
|
|
5
|
+
'self-introduction. Continue as one uninterrupted assistant and answer the request directly.';
|
|
6
|
+
const SUFFIX = `\n\n[Handoff continuation]\n${HANDOFF_CONTINUATION_DIRECTIVE}`;
|
|
7
|
+
/**
|
|
8
|
+
* Append the continuation directive to a target agent's instructions on a silent
|
|
9
|
+
* handoff. Handles every `Instructions` form: a string is suffixed; a (sync)
|
|
10
|
+
* function is wrapped so the directive is appended to its resolved text; an
|
|
11
|
+
* `AgentPrompt` object is left untouched (it cannot be safely suffixed, and is
|
|
12
|
+
* rare as a base layer); `undefined` yields the directive alone.
|
|
13
|
+
*/
|
|
14
|
+
export function applyHandoffContinuation(base) {
|
|
15
|
+
if (typeof base === 'string') {
|
|
16
|
+
return `${base}${SUFFIX}`;
|
|
17
|
+
}
|
|
18
|
+
if (typeof base === 'function') {
|
|
19
|
+
return (ctx) => {
|
|
20
|
+
const inner = base(ctx);
|
|
21
|
+
if (typeof inner !== 'string') {
|
|
22
|
+
// resolveInstructions only supports sync-string functions; match that contract.
|
|
23
|
+
throw new Error('handoff continuation: instructions function must return a string synchronously');
|
|
24
|
+
}
|
|
25
|
+
return `${inner}${SUFFIX}`;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
// AgentPrompt object → preserve as-is (do not drop the persona); undefined → directive only.
|
|
29
|
+
return base ?? HANDOFF_CONTINUATION_DIRECTIVE;
|
|
30
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** True when the recent handoff history shows A↔B oscillation at/above `threshold` hops
|
|
2
|
+
* between the same unordered pair, counting the pending from→to as the latest hop. */
|
|
3
|
+
export declare function isHandoffOscillating(history: Array<{
|
|
4
|
+
from: string;
|
|
5
|
+
to: string;
|
|
6
|
+
}>, pendingFrom: string, pendingTo: string, threshold: number): boolean;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** True when the recent handoff history shows A↔B oscillation at/above `threshold` hops
|
|
2
|
+
* between the same unordered pair, counting the pending from→to as the latest hop. */
|
|
3
|
+
export function isHandoffOscillating(history, pendingFrom, pendingTo, threshold) {
|
|
4
|
+
const pairKey = (a, b) => [a, b].sort().join('\0');
|
|
5
|
+
const target = pairKey(pendingFrom, pendingTo);
|
|
6
|
+
let count = 1; // the pending hop
|
|
7
|
+
for (let i = history.length - 1; i >= 0; i -= 1) {
|
|
8
|
+
const h = history[i];
|
|
9
|
+
if (!h)
|
|
10
|
+
break;
|
|
11
|
+
if (pairKey(h.from, h.to) === target)
|
|
12
|
+
count += 1;
|
|
13
|
+
else
|
|
14
|
+
break; // only CONSECUTIVE same-pair hops count as oscillation
|
|
15
|
+
}
|
|
16
|
+
return count >= threshold;
|
|
17
|
+
}
|