mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 06:42:03 +00:00
Add context passing between execution steps and enhance orchestration
- Add context_for_next_step parameter to CompleteStep for passing execution history - Add context parameter to CompleteTask for preserving task completion state - Update OrchestrationResult to handle context propagation between steps - Add debug color differentiation for agent types (Main, Execution, Orchestration, Planning) - Reorganize SearchTypes from Helpers to Types directory - Add justification requirement for execution deviations - Support reasonable deviations in orchestration plan validation - Refactor dependency service with TryResolve utility - Add whitespace cleanup to Semaphore class
This commit is contained in:
parent
ab9ee08281
commit
cefc408b2e
26 changed files with 529 additions and 337 deletions
|
|
@ -11,10 +11,14 @@ export const CompleteStep: IAIFunctionDefinition = {
|
|||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
context_for_next_step: {
|
||||
type: "string",
|
||||
description: "Optional contextual information needed for the next step's execution, derived from any relevant parts of the execution history so far. Analyze what the next step requires and provide only the pertinent information from previous steps (not just the most recent one). Examples: 'Files created: /notes/summary.md, /notes/details.md', 'Search found 5 entries in tags: work, personal', 'Current state: theme=dark, sidebar=collapsed'. Omit if the next step can execute independently without prior results."
|
||||
},
|
||||
confirm_completion: {
|
||||
type: "boolean",
|
||||
description: "Safety flag that must be explicitly set to true to confirm the step completion is intentional. This prevents accidental completions.",
|
||||
default: false
|
||||
type: "boolean",
|
||||
description: "Safety flag that must be explicitly set to true to confirm the step completion is intentional. This prevents accidental completions.",
|
||||
default: false
|
||||
}
|
||||
},
|
||||
required: ["confirm_completion"]
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@ export const CompleteTask: IAIFunctionDefinition = {
|
|||
description: {
|
||||
type: "string",
|
||||
description: "A summary of what was accomplished, any outputs produced, or what prevented completion."
|
||||
},
|
||||
context: {
|
||||
type: "string",
|
||||
description: "Optional contextual information that should be preserved or passed forward after task completion. Include any relevant state, findings, file paths, or intermediate results that might be useful for subsequent operations or for the user to understand what was done. Examples: 'Created 3 new notes in /Projects folder', 'Found configuration in settings.json', 'Modified files: note1.md, note2.md'"
|
||||
}
|
||||
},
|
||||
required: ["success", "description"]
|
||||
|
|
|
|||
|
|
@ -75,12 +75,14 @@ export const CancelPlanArgsSchema = z.object({
|
|||
});
|
||||
|
||||
export const CompleteStepArgsSchema = z.object({
|
||||
context_for_next_step: z.string().optional(),
|
||||
confirm_completion: z.boolean()
|
||||
});
|
||||
|
||||
export const CompleteTaskArgsSchema = z.object({
|
||||
success: z.boolean(),
|
||||
description: z.string()
|
||||
description: z.string(),
|
||||
context: z.string().optional()
|
||||
});
|
||||
|
||||
export const SubmitPlanArgsSchema = z.object({
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ If you cannot complete the task:
|
|||
- 2. Document specifically what blocked you
|
||||
- 3. Signal completion with a clear indication of failure and explanation of the blocker
|
||||
- 4. Do not leave work in a broken or half-finished state if avoidable
|
||||
- 5. If you deviated from the initial task instructions, justify why you did this
|
||||
|
||||
### Boundaries
|
||||
- You have no memory of previous conversations
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ After reviewing the execution state, you must signal exactly one outcome:
|
|||
Signal this when:
|
||||
- The most recent step succeeded
|
||||
- The results align with what the plan expected
|
||||
- The results don't align with what the plan expected but the results have been reasonably justified
|
||||
- No adjustments are needed
|
||||
|
||||
**Replan** — The plan needs adjustment.
|
||||
|
|
|
|||
9
Enums/DebugColor.ts
Normal file
9
Enums/DebugColor.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
export enum DebugColor {
|
||||
RED = "#F44336",
|
||||
ORANGE = "#FF9800",
|
||||
YELLOW = "#FFEB3B",
|
||||
GREEN = "#4CAF50",
|
||||
BLUE = "#2196F3",
|
||||
PINK = "#E91E63",
|
||||
WHITE = "#FFFFFF"
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { extractText, getDocumentProxy } from 'unpdf';
|
||||
import type { IPageText } from './SearchTypes';
|
||||
import type { IPageText } from '../Types/SearchTypes';
|
||||
import { Exception } from './Exception';
|
||||
|
||||
export async function readPDF(arrayBuffer: ArrayBuffer): Promise<IPageText[]> {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
export class Semaphore {
|
||||
|
||||
private max: number;
|
||||
private count: number;
|
||||
private readonly waitAsync: boolean;
|
||||
|
|
@ -38,4 +39,5 @@ export class Semaphore {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -9,21 +9,26 @@ import { Copy } from "Enums/Copy";
|
|||
import { Role } from "Enums/Role";
|
||||
import { sanitizeFunctionCallContent } from "Helpers/ResponseHelper";
|
||||
import type { IChatServiceCallbacks } from "Services/ChatService";
|
||||
import { Resolve } from "Services/DependencyService";
|
||||
import { Resolve, TryResolve } from "Services/DependencyService";
|
||||
import { Services } from "Services/Services";
|
||||
import type { AIFunctionService } from "./AIFunctionService";
|
||||
import type { DebugService } from "Services/DebugService";
|
||||
import { DebugColor } from "Enums/DebugColor";
|
||||
|
||||
export class AIController {
|
||||
|
||||
protected ai: IAIClass | undefined;
|
||||
protected readonly aiPrompt: IPrompt;
|
||||
protected readonly aiFunctionService: AIFunctionService;
|
||||
protected readonly debugService: DebugService | undefined;
|
||||
|
||||
private onSaveConversation?: (conversation: Conversation) => Promise<void>;
|
||||
|
||||
public constructor() {
|
||||
this.aiPrompt = Resolve<IPrompt>(Services.IPrompt);
|
||||
this.aiFunctionService = Resolve<AIFunctionService>(Services.AIFunctionService);
|
||||
this.debugService = TryResolve<DebugService>(Services.DebugService);
|
||||
this.setDebugColor();
|
||||
}
|
||||
|
||||
public resolveAIProvider() {
|
||||
|
|
@ -91,6 +96,10 @@ export class AIController {
|
|||
}
|
||||
}
|
||||
|
||||
protected setDebugColor() {
|
||||
this.debugService?.setDebugColor(DebugColor.WHITE);
|
||||
}
|
||||
|
||||
private async saveConversation(agentType: AgentType, conversation: Conversation) {
|
||||
if (agentType === AgentType.Main) {
|
||||
await this.onSaveConversation?.(conversation);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import type { FileSystemService } from "../FileSystemService";
|
|||
import { AIFunction, fromString } from "Enums/AIFunction";
|
||||
import { AIFunctionResponse } from "AIClasses/FunctionDefinitions/AIFunctionResponse";
|
||||
import type { AIFunctionCall } from "AIClasses/AIFunctionCall";
|
||||
import type { ISearchMatch } from "../../Helpers/SearchTypes";
|
||||
import type { ISearchMatch } from "../../Types/SearchTypes";
|
||||
import { AbortService } from "../AbortService";
|
||||
import { normalizePath, TAbstractFile, TFile } from "obsidian";
|
||||
import { Exception } from "Helpers/Exception";
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { AIFunctionResponse } from "AIClasses/FunctionDefinitions/AIFunctionResp
|
|||
import { Exception } from "Helpers/Exception";
|
||||
import { AIFunctionDefinitions } from "AIClasses/FunctionDefinitions/AIFunctionDefinitions";
|
||||
import { Copy, replaceCopy } from "Enums/Copy";
|
||||
import { DebugColor } from "Enums/DebugColor";
|
||||
|
||||
export class ExecutionAgent extends AIController {
|
||||
|
||||
|
|
@ -80,4 +81,8 @@ export class ExecutionAgent extends AIController {
|
|||
this.ai.toolDefinitions = AIFunctionDefinitions.executionAgentDefinitions();
|
||||
}
|
||||
|
||||
protected override setDebugColor(): void {
|
||||
this.debugService?.setDebugColor(DebugColor.GREEN);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ import type { AIFunctionCall } from "AIClasses/AIFunctionCall";
|
|||
import { OrchestrationAgent } from "./OrchestrationAgent";
|
||||
import { ConversationContent } from "Conversations/ConversationContent";
|
||||
import { Role } from "Enums/Role";
|
||||
import { DebugColor } from "Enums/DebugColor";
|
||||
|
||||
export class MainAgent extends AIController {
|
||||
|
||||
|
|
@ -85,5 +86,9 @@ export class MainAgent extends AIController {
|
|||
this.ai.userInstruction = await this.aiPrompt.userInstruction();
|
||||
this.ai.toolDefinitions = AIFunctionDefinitions.agentDefinitions(allowDestructiveActions, planningMode);
|
||||
}
|
||||
|
||||
protected override setDebugColor(): void {
|
||||
this.debugService?.setDebugColor(DebugColor.BLUE);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import { OrchestrationResult } from "Types/OrchestrationResult";
|
|||
import { AgentType } from "Enums/AgentType";
|
||||
import { AIFunction, isAIFunction } from "Enums/AIFunction";
|
||||
import { AIFunctionResponse } from "AIClasses/FunctionDefinitions/AIFunctionResponse";
|
||||
import { DebugColor } from "Enums/DebugColor";
|
||||
|
||||
export class OrchestrationAgent extends AIController {
|
||||
|
||||
|
|
@ -71,6 +72,12 @@ export class OrchestrationAgent extends AIController {
|
|||
const orchestrationResult = await this.runOrchestrationAgentLoop(planningConversation, callbacks);
|
||||
|
||||
if (orchestrationResult.continue) {
|
||||
if (orchestrationResult.continueContext && index + 1 < executionPlan.executionSteps.length) {
|
||||
const nextStep = executionPlan.executionSteps[index + 1];
|
||||
nextStep.context = nextStep.context
|
||||
? nextStep.context.concat("\n\n", orchestrationResult.continueContext)
|
||||
: orchestrationResult.continueContext;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (orchestrationResult.abort) {
|
||||
|
|
@ -144,7 +151,7 @@ export class OrchestrationAgent extends AIController {
|
|||
{ message: "Step Completed" },
|
||||
functionCall.toolId
|
||||
));
|
||||
orchestrationResult = new OrchestrationResult({ continue: true });
|
||||
orchestrationResult = new OrchestrationResult({ continue: true, continueContext: parseResult.data.context_for_next_step });
|
||||
return { shouldExit: true }
|
||||
}
|
||||
|
||||
|
|
@ -213,5 +220,9 @@ export class OrchestrationAgent extends AIController {
|
|||
this.ai.userInstruction = ""; // do not include user instruction for orchestration agent
|
||||
this.ai.toolDefinitions = AIFunctionDefinitions.orchestrationAgentDefinitions();
|
||||
}
|
||||
|
||||
protected override setDebugColor(): void {
|
||||
this.debugService?.setDebugColor(DebugColor.ORANGE);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ import { AIFunctionDefinitions } from "AIClasses/FunctionDefinitions/AIFunctionD
|
|||
import { Copy } from "Enums/Copy";
|
||||
import { ConversationContent } from "Conversations/ConversationContent";
|
||||
import { Role } from "Enums/Role";
|
||||
import { DebugColor } from "Enums/DebugColor";
|
||||
|
||||
export class PlanningAgent extends AIController {
|
||||
|
||||
|
|
@ -107,4 +108,8 @@ export class PlanningAgent extends AIController {
|
|||
this.ai.toolDefinitions = AIFunctionDefinitions.planningAgentDefinitions();
|
||||
}
|
||||
|
||||
protected override setDebugColor(): void {
|
||||
this.debugService?.setDebugColor(DebugColor.YELLOW);
|
||||
}
|
||||
|
||||
}
|
||||
27
Services/DebugService.ts
Normal file
27
Services/DebugService.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { DebugColor } from "Enums/DebugColor";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
debugServiceLog: (level: string, message: string) => void;
|
||||
}
|
||||
}
|
||||
|
||||
export class DebugService {
|
||||
|
||||
private debugColor: DebugColor = DebugColor.WHITE;
|
||||
|
||||
public constructor() {
|
||||
window.debugServiceLog = (level: string, message: string) => {
|
||||
console.log(`%c${level}: ${message}`, `color:${this.debugColor};`);
|
||||
};
|
||||
}
|
||||
|
||||
public setDebugColor(debugColor: DebugColor): void {
|
||||
this.debugColor = debugColor;
|
||||
}
|
||||
|
||||
public log(level: string, message: string): void {
|
||||
window.debugServiceLog(level, message);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ export function RegisterTransient<T>(type: symbol, factory: () => T) {
|
|||
|
||||
export function Resolve<T>(type: symbol): T {
|
||||
const service = services.get(type);
|
||||
|
||||
if (!service) {
|
||||
Exception.throw(`Service not found for type: ${type.description}`);
|
||||
}
|
||||
|
|
@ -26,6 +27,12 @@ export function Resolve<T>(type: symbol): T {
|
|||
return service as T;
|
||||
}
|
||||
|
||||
export function TryResolve<T>(type: symbol): T | undefined {
|
||||
if (services.has(type)) {
|
||||
return Resolve<T>(type);
|
||||
}
|
||||
}
|
||||
|
||||
export function DeregisterAllServices() {
|
||||
services.forEach((service) => {
|
||||
if (service && typeof service === "object" && "unload" in service) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { TAbstractFile, TFile, TFolder } from "obsidian";
|
|||
import { Resolve } from "./DependencyService";
|
||||
import { Services } from "./Services";
|
||||
import type { VaultService } from "./VaultService";
|
||||
import type { ISearchMatch } from "../Helpers/SearchTypes";
|
||||
import type { ISearchMatch } from "../Types/SearchTypes";
|
||||
import { Exception } from "Helpers/Exception";
|
||||
|
||||
export class FileSystemService {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ import { ClaudeFileService } from "AIClasses/Claude/ClaudeFileService";
|
|||
import { GeminiFileService } from "AIClasses/Gemini/GeminiFileService";
|
||||
import { OpenAIFileService } from "AIClasses/OpenAI/OpenAIFileService";
|
||||
import { MainAgent } from "./AIServices/MainAgent";
|
||||
import { Environment } from "Enums/Environment";
|
||||
import { DebugService } from "./DebugService";
|
||||
|
||||
export async function RegisterPlugin(plugin: VaultkeeperAIPlugin) {
|
||||
RegisterSingleton<VaultkeeperAIPlugin>(Services.VaultkeeperAIPlugin, plugin);
|
||||
|
|
@ -45,6 +47,10 @@ export async function RegisterPlugin(plugin: VaultkeeperAIPlugin) {
|
|||
}
|
||||
|
||||
export function RegisterDependencies() {
|
||||
if (process.env.NODE_ENV === Environment.DEV) {
|
||||
RegisterTransient<DebugService | undefined>(Services.DebugService, () => new DebugService());
|
||||
}
|
||||
|
||||
RegisterSingleton<EventService>(Services.EventService, new EventService());
|
||||
RegisterSingleton<AbortService>(Services.AbortService, new AbortService());
|
||||
RegisterSingleton<HTMLService>(Services.HTMLService, new HTMLService());
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export class Services {
|
|||
static SanitiserService = Symbol("SanitiserService");
|
||||
static InputService = Symbol("InputService");
|
||||
static DiffService = Symbol("DiffService");
|
||||
static DebugService = Symbol("DebugService");
|
||||
|
||||
// stores
|
||||
static SearchStateStore = Symbol("SearchStateStore");
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import type VaultkeeperAIPlugin from "main";
|
|||
import { Path } from "Enums/Path";
|
||||
import { pathExtname, randomSample, shuffleArray } from "Helpers/Helpers";
|
||||
import { StringTools } from "Helpers/StringTools";
|
||||
import type { IPageText, ISearchMatch, ISearchSnippet } from "../Helpers/SearchTypes";
|
||||
import type { IPageText, ISearchMatch, ISearchSnippet } from "../Types/SearchTypes";
|
||||
import type { SanitiserService } from "./SanitiserService";
|
||||
import { FileEvent } from "Enums/FileEvent";
|
||||
import type { SettingsService } from "./SettingsService";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
type OrchestrationResultInit = {
|
||||
continue?: boolean;
|
||||
continueContext?: string;
|
||||
abort?: boolean;
|
||||
abortContext?: string;
|
||||
replan?: boolean;
|
||||
|
|
@ -9,6 +10,7 @@ type OrchestrationResultInit = {
|
|||
export class OrchestrationResult {
|
||||
|
||||
public continue: boolean;
|
||||
public continueContext: string;
|
||||
public abort: boolean;
|
||||
public abortContext: string;
|
||||
public replan: boolean;
|
||||
|
|
@ -16,6 +18,7 @@ export class OrchestrationResult {
|
|||
|
||||
constructor(init: OrchestrationResultInit) {
|
||||
this.continue = init.continue ?? false;
|
||||
this.continueContext = init.continueContext ?? "";
|
||||
this.abort = init.abort ?? false;
|
||||
this.abortContext = init.abortContext ?? "";
|
||||
this.replan = init.replan ?? false;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { VaultService } from '../../Services/VaultService';
|
|||
import { RegisterSingleton, DeregisterAllServices } from '../../Services/DependencyService';
|
||||
import { Services } from '../../Services/Services';
|
||||
import { TFile, TFolder, TAbstractFile } from 'obsidian';
|
||||
import type { ISearchMatch } from '../../Helpers/SearchTypes';
|
||||
import type { ISearchMatch } from '../../Types/SearchTypes';
|
||||
import { Exception } from '../../Helpers/Exception';
|
||||
|
||||
// Helper function to create mock TFile
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { SettingsService, type IVaultkeeperAISettings } from '../../Services/Set
|
|||
import { AIProviderModel } from '../../Enums/ApiProvider';
|
||||
import { Exception } from '../../Helpers/Exception';
|
||||
import * as PDFHelper from '../../Helpers/PDFHelper';
|
||||
import type { IPageText } from '../../Helpers/SearchTypes';
|
||||
import type { IPageText } from '../../Types/SearchTypes';
|
||||
|
||||
/**
|
||||
* PDF-SPECIFIC TESTS FOR VAULTSERVICE
|
||||
|
|
|
|||
714
package-lock.json
generated
714
package-lock.json
generated
File diff suppressed because it is too large
Load diff
26
package.json
26
package.json
|
|
@ -23,39 +23,39 @@
|
|||
"@eslint/js": "^9.39.2",
|
||||
"@testing-library/svelte": "^5.3.1",
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/node": "^25.0.3",
|
||||
"@types/node": "^25.0.10",
|
||||
"@types/path-browserify": "^1.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "8.52.0",
|
||||
"@typescript-eslint/parser": "8.52.0",
|
||||
"@vitest/ui": "^4.0.16",
|
||||
"@typescript-eslint/eslint-plugin": "8.54.0",
|
||||
"@typescript-eslint/parser": "8.54.0",
|
||||
"@vitest/ui": "^4.0.18",
|
||||
"builtin-modules": "5.0.0",
|
||||
"esbuild": "^0.27.2",
|
||||
"esbuild-svelte": "^0.9.4",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-plugin-eslint-comments": "^3.2.0",
|
||||
"eslint-plugin-obsidianmd": "^0.1.9",
|
||||
"happy-dom": "^20.1.0",
|
||||
"happy-dom": "^20.3.9",
|
||||
"obsidian": "latest",
|
||||
"svelte": "^5.46.1",
|
||||
"svelte": "^5.48.4",
|
||||
"svelte-check": "^4.3.5",
|
||||
"svelte-preprocess": "^6.0.3",
|
||||
"tslib": "2.8.1",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.16"
|
||||
"vitest": "^4.0.18"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.71.2",
|
||||
"@google/genai": "^1.34.0",
|
||||
"@google/genai": "^1.38.0",
|
||||
"@shikijs/rehype": "^3.21.0",
|
||||
"core-js": "^3.47.0",
|
||||
"diff": "^8.0.2",
|
||||
"core-js": "^3.48.0",
|
||||
"diff": "^8.0.3",
|
||||
"diff2html": "^3.4.55",
|
||||
"express": "^5.2.1",
|
||||
"fuzzysort": "^3.1.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"katex": "^0.16.27",
|
||||
"katex": "^0.16.28",
|
||||
"lowlight": "^3.3.0",
|
||||
"openai": "^6.15.0",
|
||||
"openai": "^6.16.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"regex-parser": "^2.3.1",
|
||||
"rehype-highlight": "^7.0.2",
|
||||
|
|
@ -70,6 +70,6 @@
|
|||
"unified": "^11.0.5",
|
||||
"unpdf": "^1.4.0",
|
||||
"uuid": "^13.0.0",
|
||||
"zod": "^4.3.5"
|
||||
"zod": "^4.3.6"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue