@mikode13/harness 5.4.0 → 6.0.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 +127 -3
- package/dist/agent/domain/agent.d.ts +30 -3
- package/dist/agent/domain/agent.js.map +1 -1
- package/dist/engines/domain/model/llmAgent.js +9 -8
- package/dist/engines/domain/model/llmAgent.js.map +1 -1
- package/dist/engines/domain/model/stepContext.d.ts +1 -1
- package/dist/engines/domain/model/stepContext.js +8 -1
- package/dist/engines/domain/model/stepContext.js.map +1 -1
- package/dist/factory/infrastructure/agentLLMFactory.d.ts +17 -2
- package/dist/factory/infrastructure/agentLLMFactory.js +57 -27
- package/dist/factory/infrastructure/agentLLMFactory.js.map +1 -1
- package/dist/factory/infrastructure/workflowFactory.d.ts +9 -0
- package/dist/factory/infrastructure/workflowFactory.js +42 -0
- package/dist/factory/infrastructure/workflowFactory.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/llm/domain/llm.d.ts +2 -2
- package/dist/llm/domain/llm.js.map +1 -1
- package/dist/llm/domain/message.d.ts +17 -1
- package/dist/llm/domain/message.js +14 -1
- package/dist/llm/domain/message.js.map +1 -1
- package/dist/llm/infrastructure/claudeLLMClient.d.ts +5 -2
- package/dist/llm/infrastructure/claudeLLMClient.js +61 -5
- package/dist/llm/infrastructure/claudeLLMClient.js.map +1 -1
- package/dist/llm/infrastructure/openAILLMClient.d.ts +5 -2
- package/dist/llm/infrastructure/openAILLMClient.js +25 -2
- package/dist/llm/infrastructure/openAILLMClient.js.map +1 -1
- package/dist/orchestration/domain/model/orchestratorAgent.d.ts +1 -1
- package/dist/orchestration/domain/model/orchestratorAgent.js.map +1 -1
- package/dist/orchestration/infrastructure/model/reviewerDecisionValidator.d.ts +1 -1
- package/dist/orchestration/infrastructure/model/reviewerDecisionValidator.js.map +1 -1
- package/dist/shared/domain/isAbortError.d.ts +1 -0
- package/dist/shared/domain/isAbortError.js +2 -1
- package/dist/shared/domain/isAbortError.js.map +1 -1
- package/dist/shared/domain/validator.d.ts +4 -0
- package/dist/shared/domain/validator.js.map +1 -0
- package/dist/tools/infrastructure/workspaceTools.d.ts +1 -0
- package/dist/tools/infrastructure/workspaceTools.js +32 -10
- package/dist/tools/infrastructure/workspaceTools.js.map +1 -1
- package/dist/workflow/domain/workflow.d.ts +100 -0
- package/dist/workflow/domain/workflow.js +12 -0
- package/dist/workflow/domain/workflow.js.map +1 -0
- package/dist/workflow/domain/workflowRunner.d.ts +54 -0
- package/dist/workflow/domain/workflowRunner.js +271 -0
- package/dist/workflow/domain/workflowRunner.js.map +1 -0
- package/package.json +1 -1
- package/dist/orchestration/domain/interface/validator.d.ts +0 -3
- package/dist/orchestration/domain/interface/validator.js.map +0 -1
- /package/dist/{orchestration/domain/interface → shared/domain}/validator.js +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orchestratorAgent.js","sourceRoot":"","sources":["../../../../src/orchestration/domain/model/orchestratorAgent.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tAgent,\n\tAgentResponse,\n\tProgressAgent,\n\tProgressEvent,\n\tRunOptions,\n} from '#src/agent/domain/agent';\nimport { addTokens, type Tokens } from '#src/shared/domain/tokens';\nimport { RecoverableError, UnrecoverableError, withSpentTokens } from '#src/shared/domain/errors';\nimport type { ReviewerDecision } from './reviewerDecision.ts';\nimport type { Validator } from '../interface/validator.ts';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport {\n\tclassifyHostFailure,\n\tdescribeFailure,\n\ttreatErrors,\n} from '#src/shared/domain/providerFailure';\nimport {\n\tRunContext,\n\ttype RunEnd,\n\trunContextOf,\n\twithRunContext,\n} from '#src/agent/domain/runContext';\nimport { isAbortError } from '#src/shared/domain/isAbortError';\n\n// Each role's standing instructions, kept apart from the data of a round so an agent with a\n// system prompt can hold them there instead of receiving them again in every prompt. An agent\n// without one gets them at the head of each prompt; see `InstructedAgent`.\nconst conciseCommunication =\n\t'Communicate tersely and professionally. In reasoning updates, state only the next concrete action or a verified finding, in one short sentence. Do not narrate routine intentions, uncertainty, self-reflection, encouragement, or a desire to be thorough. Do not restate tool results. Once you decide what to do, act immediately. Use only the detail required by your role in the final response.';\n\nexport const plannerInstructions = `You are the planner agent. Read the relevant repository code and create a concise, engineering-grade plan for the executor. Cover the requested behaviour, structural issues, affected files or symbols, API contracts and boundaries, important failure paths, and meaningful tests. Keep the plan focused on the current request and do not require an architecture redesign yet. ${conciseCommunication}`;\n\nconst executorTask = `You are the executor agent. Read the relevant repository code and implement the original user request according to the planner's current plan. Preserve contracts and boundaries, handle important failure paths, keep the change focused, and add or update meaningful tests. Inspect and change the code; do not merely describe what should be done. ${conciseCommunication}`;\n\n// For an executor that can run the project's tests and checks: an Agent SDK one, which has a\n// shell, or a model-API one given commands. The harness cannot tell which command runs the\n// tests, so any command counts.\nexport const executorInstructions = `${executorTask} Before you finish, run the project's tests and checks, and finish only when they pass. Once they pass and the plan is carried out, finish at once: do not add work beyond the plan, because the reviewer asks for what is missing. If you cannot make them pass, finish by saying which ones fail and why.`;\n\n// For a model-API executor with no command, which cannot run anything: told so, it neither\n// spends its steps looking for a way to test nor claims a result it could not have.\nexport const executorInstructionsWithoutCommands = `${executorTask} You have no command that runs the project's tests or checks, so you cannot run them. Once the plan is carried out, finish at once: do not add work beyond the plan, because the reviewer asks for what is missing. Say in your summary that the tests and checks were not run.`;\n\nconst reviewerTask = `Review the code. Independently inspect the repository, the current diff, and relevant surrounding code; do not rely on the executor's narrative. Judge it against the original request, and any issue it names, first: check that the plan is right for it, and that the change fulfills it. Check correctness and logic errors, important failure paths, unused or artificial abstractions, races or shared state, API contract breaks, boundary violations, regressions, scope, and test quality. If it is correct, respond with JSON only: {\"decision\":\"approved\"}. If it is not correct, respond with JSON only: {\"decision\":\"rejected\",\"feedback\":\"list concrete, prioritized findings and the required direction for each\"}. The feedback must contain actionable engineering findings, not a general summary. ${conciseCommunication}`;\n\n// Each goes with the executor instructions of the same name.\nexport const reviewerInstructions = `You are the reviewer agent. The executor has already run the tests and checks, and finished only when they passed: do not run them again. ${reviewerTask}`;\n\nexport const reviewerInstructionsWithoutCommands = `You are the reviewer agent. Nobody has run the tests or checks: the executor had no command for them. Judge from the code whether the tests it added or changed would pass, and do not approve a change whose tests could not. ${reviewerTask}`;\n\nconst getPlannerPrompt = (userPrompt: string, previousFailureReason?: string) => {\n\tconst feedback = previousFailureReason\n\t\t? `\\n\\nFeedback from the previous attempt:\\n---\\n${previousFailureReason}\\n---`\n\t\t: '';\n\n\treturn `Original user request:\n---\n${userPrompt}\n---${feedback}`;\n};\n\nconst getExecutorPrompt = (userPrompt: string, plannerPrompt: string) =>\n\t`Original user request:\n---\n${userPrompt}\n---\n\nCurrent implementation plan:\n---\n${plannerPrompt}\n---`;\n\nconst getReviewerPrompt = (\n\tuserPrompt: string,\n\tplannerPrompt: string,\n\texecutorResult: string,\n\tparseFailureReason?: string,\n) => {\n\tconst retryNotice = parseFailureReason\n\t\t? `\\n\\nYour previous response could not be used: ${parseFailureReason} Respond with JSON only, matching the schema exactly, with no surrounding text and no markdown code fences.`\n\t\t: '';\n\n\treturn `Original user request:\n---\n${userPrompt}\n---\n\nPlanner's current plan:\n---\n${plannerPrompt}\n---\n\nExecutor response (context only; verify it independently):\n---\n${executorResult}\n---${retryNotice}`;\n};\n\ninterface RunTotals {\n\ttokens: Tokens | undefined;\n\t/** A role answered without usage, so the run's total cannot be known. */\n\tunreported: boolean;\n}\n\n// Every role's response counts, even an empty one that starts another round: it was billed.\nfunction addToTotals(totals: RunTotals, response: AgentResponse): void {\n\tif (!response.tokens) totals.unreported = true;\n\ttotals.tokens = addTokens(totals.tokens, response.tokens);\n}\n\n/** Unknown rather than partial: a sum missing a billed call must not look complete. */\nfunction runTokens(totals: RunTotals): Tokens | undefined {\n\treturn totals.unreported ? undefined : totals.tokens;\n}\n\nfunction stripCodeFence(text: string): string {\n\tconst match = /^```(?:json)?\\s*([\\s\\S]*?)\\s*```$/.exec(text.trim());\n\treturn match?.[1] ?? text;\n}\n\nfunction parseReviewerDecision(\n\tresponse: AgentResponse,\n\treviewerDecisionValidator: Validator<ReviewerDecision>,\n): ReviewerDecision {\n\tif (!response.response) {\n\t\tthrow new RecoverableError(\"There's no decision from the reviewer, something went wrong\", {\n\t\t\tcause: 'Reviewer decision is missing.',\n\t\t});\n\t}\n\n\tlet parsedResponse: unknown;\n\ttry {\n\t\tparsedResponse = JSON.parse(stripCodeFence(response.response));\n\t} catch {\n\t\tthrow new RecoverableError('Reviewer returned an invalid decision', {\n\t\t\tcause: 'Reviewer response must be valid JSON.',\n\t\t});\n\t}\n\n\tconst decision = reviewerDecisionValidator.validate(parsedResponse);\n\tif (!decision) {\n\t\tthrow new RecoverableError('Reviewer returned an invalid decision', {\n\t\t\tcause: 'Reviewer response did not match the decision schema.',\n\t\t});\n\t}\n\n\treturn decision;\n}\n\nexport class OrchestratorAgent implements Agent {\n\tprivate plannerAgent: Agent;\n\tprivate executorAgent: Agent;\n\tprivate reviewerAgent: Agent;\n\tprivate reviewerDecisionValidator: Validator<ReviewerDecision>;\n\tprivate maxAttempts: number;\n\tprivate logger: ILogger;\n\n\tconstructor({\n\t\tplannerAgent,\n\t\texecutorAgent,\n\t\treviewerAgent,\n\t\treviewerDecisionValidator,\n\t\tmaxAttempts = 3,\n\t\tlogger,\n\t}: {\n\t\tplannerAgent: Agent;\n\t\texecutorAgent: Agent;\n\t\treviewerAgent: Agent;\n\t\treviewerDecisionValidator: Validator<ReviewerDecision>;\n\t\tmaxAttempts?: number;\n\t\tlogger: ILogger;\n\t}) {\n\t\tif (!Number.isInteger(maxAttempts) || maxAttempts < 1) {\n\t\t\tthrow new RangeError('maxAttempts must be a positive integer');\n\t\t}\n\n\t\tthis.plannerAgent = plannerAgent;\n\t\tthis.executorAgent = executorAgent;\n\t\tthis.reviewerAgent = reviewerAgent;\n\t\tthis.reviewerDecisionValidator = reviewerDecisionValidator;\n\t\tthis.maxAttempts = maxAttempts;\n\t\tthis.logger = logger;\n\t}\n\n\t/**\n\t * Every role, round and retry of one run shares one run context, so what the executor writes\n\t * is one run of the workspace's history, and the planner's and reviewer's turns are tied to\n\t * it. The orchestrator ends that context with the run, unless an outer agent owns it.\n\t */\n\tasync run(prompt: string, options: RunOptions): Promise<AgentResponse> {\n\t\tconst start = Date.now();\n\t\t// Per invocation, not per instance: the CLI keeps one orchestrator for a whole session.\n\t\tconst totals: RunTotals = { tokens: undefined, unreported: false };\n\t\tconst inherited = runContextOf(options);\n\t\tconst context = inherited ?? new RunContext();\n\t\tlet end: RunEnd = 'failed';\n\n\t\ttry {\n\t\t\tawait this.runRounds(prompt, inherited ? options : withRunContext(options, context), totals);\n\t\t\tend = 'completed';\n\t\t} catch (error) {\n\t\t\tif (isAbortError(error)) end = 'cancelled';\n\t\t\t// A failing role carries its own tokens; the roles before it are in the totals.\n\t\t\tthrow withSpentTokens(error, totals.tokens, totals.unreported);\n\t\t} finally {\n\t\t\tif (!inherited) await this.finishContext(context, end);\n\t\t}\n\n\t\t// Read once the run ended: a run that changed nothing left the history then.\n\t\tconst { runId } = context.historyRun;\n\t\treturn {\n\t\t\tresponse: 'All job has finished',\n\t\t\ttokens: runTokens(totals),\n\t\t\t// The whole run's wall clock, so it matches what the consumer waited.\n\t\t\tduration: (Date.now() - start) / 1000,\n\t\t\t...(!inherited && runId ? { runId } : {}),\n\t\t};\n\t}\n\n\t/** The run's records end here, but its result stands: what it changed is already on disk. */\n\tprivate async finishContext(context: RunContext, end: RunEnd): Promise<void> {\n\t\ttry {\n\t\t\tawait context.finish(end);\n\t\t} catch (error) {\n\t\t\tthis.warn(`The run could not close its records: ${describeFailure(error)}`);\n\t\t}\n\t}\n\n\tprivate async runRounds(prompt: string, options: RunOptions, totals: RunTotals): Promise<void> {\n\t\tlet lastFailureReason: string | undefined;\n\n\t\tfor (let attempt = 1; attempt <= this.maxAttempts; attempt++) {\n\t\t\tconst isLastAttempt = attempt === this.maxAttempts;\n\n\t\t\tconst plannerResponse = await this.plannerAgent.run(\n\t\t\t\tgetPlannerPrompt(prompt, lastFailureReason),\n\t\t\t\tthis.optionsForRole(options, 'planner'),\n\t\t\t);\n\t\t\taddToTotals(totals, plannerResponse);\n\n\t\t\tif (!plannerResponse.response) {\n\t\t\t\tlastFailureReason = 'The planner produced no response.';\n\t\t\t\tif (isLastAttempt) {\n\t\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: lastFailureReason });\n\t\t\t\t}\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Attempt ${String(attempt)}/${String(this.maxAttempts)}: the planner produced no response; starting another round`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst executorResponse = await this.executorAgent.run(\n\t\t\t\tgetExecutorPrompt(prompt, plannerResponse.response),\n\t\t\t\tthis.optionsForRole(options, 'executor'),\n\t\t\t);\n\t\t\taddToTotals(totals, executorResponse);\n\n\t\t\tif (!executorResponse.response) {\n\t\t\t\tlastFailureReason = 'The executor produced no response.';\n\t\t\t\tif (isLastAttempt) {\n\t\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: lastFailureReason });\n\t\t\t\t}\n\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Attempt ${String(attempt)}/${String(this.maxAttempts)}: the executor produced no response; starting another round`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst reviewerDecision = await this.getReviewerDecision(\n\t\t\t\tprompt,\n\t\t\t\tplannerResponse.response,\n\t\t\t\texecutorResponse.response,\n\t\t\t\toptions,\n\t\t\t\ttotals,\n\t\t\t);\n\n\t\t\tif (reviewerDecision.decision === 'approved') return;\n\n\t\t\tlastFailureReason = reviewerDecision.feedback;\n\t\t\tif (isLastAttempt) {\n\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: lastFailureReason });\n\t\t\t}\n\n\t\t\tthis.warn(\n\t\t\t\t`Attempt ${String(attempt)}/${String(this.maxAttempts)}: the reviewer rejected the round; starting another with its feedback`,\n\t\t\t\tlastFailureReason,\n\t\t\t);\n\t\t}\n\n\t\tthrow new UnrecoverableError('Max attempts exhausted', {\n\t\t\tcause: lastFailureReason ?? 'Unknown failure.',\n\t\t});\n\t}\n\n\t// Retries only the reviewer call on a malformed decision, instead of redoing\n\t// planning/execution — a bad or unparsable reviewer response is not evidence\n\t// the plan or the implementation were wrong.\n\tprivate async getReviewerDecision(\n\t\tuserPrompt: string,\n\t\tplannerPrompt: string,\n\t\texecutorResult: string,\n\t\toptions: RunOptions,\n\t\ttotals: RunTotals,\n\t): Promise<ReviewerDecision> {\n\t\tlet parseFailureReason: string | undefined;\n\n\t\tfor (let attempt = 1; attempt <= this.maxAttempts; attempt++) {\n\t\t\tconst reviewerResponse = await this.reviewerAgent.run(\n\t\t\t\tgetReviewerPrompt(userPrompt, plannerPrompt, executorResult, parseFailureReason),\n\t\t\t\tthis.optionsForRole(options, 'reviewer'),\n\t\t\t);\n\n\t\t\taddToTotals(totals, reviewerResponse);\n\n\t\t\ttry {\n\t\t\t\treturn parseReviewerDecision(reviewerResponse, this.reviewerDecisionValidator);\n\t\t\t} catch (error) {\n\t\t\t\tif (!(error instanceof RecoverableError)) throw error;\n\n\t\t\t\tparseFailureReason = error.cause;\n\t\t\t\tif (attempt === this.maxAttempts) {\n\t\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: parseFailureReason });\n\t\t\t\t}\n\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Reviewer decision ${String(attempt)}/${String(this.maxAttempts)} was unusable; asking the reviewer again`,\n\t\t\t\t\tparseFailureReason,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tthrow new UnrecoverableError('Max attempts exhausted', {\n\t\t\tcause: parseFailureReason ?? 'Unknown failure.',\n\t\t});\n\t}\n\n\tprivate warn(...args: unknown[]): void {\n\t\ttreatErrors(\n\t\t\t() => {\n\t\t\t\tthis.logger.warn(...args);\n\t\t\t},\n\t\t\tclassifyHostFailure,\n\t\t\t'Orchestrator logger failed while reporting a retry',\n\t\t);\n\t}\n\n\t/**\n\t * Keep the options object extensible and the private run context intact while making the\n\t * orchestrator's attribution authoritative over anything a nested agent supplied.\n\t */\n\tprivate optionsForRole(options: RunOptions, agent: ProgressAgent): RunOptions {\n\t\tconst onProgress = options.onProgress;\n\t\treturn {\n\t\t\t...options,\n\t\t\tonProgress: onProgress\n\t\t\t\t? (event: ProgressEvent) => {\n\t\t\t\t\t\ttreatErrors(\n\t\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\t\tonProgress({ ...event, agent });\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tclassifyHostFailure,\n\t\t\t\t\t\t\t'Orchestrator progress callback failed',\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t: undefined,\n\t\t};\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"orchestratorAgent.js","sourceRoot":"","sources":["../../../../src/orchestration/domain/model/orchestratorAgent.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tAgent,\n\tAgentResponse,\n\tProgressAgent,\n\tProgressEvent,\n\tRunOptions,\n} from '#src/agent/domain/agent';\nimport { addTokens, type Tokens } from '#src/shared/domain/tokens';\nimport { RecoverableError, UnrecoverableError, withSpentTokens } from '#src/shared/domain/errors';\nimport type { ReviewerDecision } from './reviewerDecision.ts';\nimport type { Validator } from '#src/shared/domain/validator';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport {\n\tclassifyHostFailure,\n\tdescribeFailure,\n\ttreatErrors,\n} from '#src/shared/domain/providerFailure';\nimport {\n\tRunContext,\n\ttype RunEnd,\n\trunContextOf,\n\twithRunContext,\n} from '#src/agent/domain/runContext';\nimport { isAbortError } from '#src/shared/domain/isAbortError';\n\n// Each role's standing instructions, kept apart from the data of a round so an agent with a\n// system prompt can hold them there instead of receiving them again in every prompt. An agent\n// without one gets them at the head of each prompt; see `InstructedAgent`.\nconst conciseCommunication =\n\t'Communicate tersely and professionally. In reasoning updates, state only the next concrete action or a verified finding, in one short sentence. Do not narrate routine intentions, uncertainty, self-reflection, encouragement, or a desire to be thorough. Do not restate tool results. Once you decide what to do, act immediately. Use only the detail required by your role in the final response.';\n\nexport const plannerInstructions = `You are the planner agent. Read the relevant repository code and create a concise, engineering-grade plan for the executor. Cover the requested behaviour, structural issues, affected files or symbols, API contracts and boundaries, important failure paths, and meaningful tests. Keep the plan focused on the current request and do not require an architecture redesign yet. ${conciseCommunication}`;\n\nconst executorTask = `You are the executor agent. Read the relevant repository code and implement the original user request according to the planner's current plan. Preserve contracts and boundaries, handle important failure paths, keep the change focused, and add or update meaningful tests. Inspect and change the code; do not merely describe what should be done. ${conciseCommunication}`;\n\n// For an executor that can run the project's tests and checks: an Agent SDK one, which has a\n// shell, or a model-API one given commands. The harness cannot tell which command runs the\n// tests, so any command counts.\nexport const executorInstructions = `${executorTask} Before you finish, run the project's tests and checks, and finish only when they pass. Once they pass and the plan is carried out, finish at once: do not add work beyond the plan, because the reviewer asks for what is missing. If you cannot make them pass, finish by saying which ones fail and why.`;\n\n// For a model-API executor with no command, which cannot run anything: told so, it neither\n// spends its steps looking for a way to test nor claims a result it could not have.\nexport const executorInstructionsWithoutCommands = `${executorTask} You have no command that runs the project's tests or checks, so you cannot run them. Once the plan is carried out, finish at once: do not add work beyond the plan, because the reviewer asks for what is missing. Say in your summary that the tests and checks were not run.`;\n\nconst reviewerTask = `Review the code. Independently inspect the repository, the current diff, and relevant surrounding code; do not rely on the executor's narrative. Judge it against the original request, and any issue it names, first: check that the plan is right for it, and that the change fulfills it. Check correctness and logic errors, important failure paths, unused or artificial abstractions, races or shared state, API contract breaks, boundary violations, regressions, scope, and test quality. If it is correct, respond with JSON only: {\"decision\":\"approved\"}. If it is not correct, respond with JSON only: {\"decision\":\"rejected\",\"feedback\":\"list concrete, prioritized findings and the required direction for each\"}. The feedback must contain actionable engineering findings, not a general summary. ${conciseCommunication}`;\n\n// Each goes with the executor instructions of the same name.\nexport const reviewerInstructions = `You are the reviewer agent. The executor has already run the tests and checks, and finished only when they passed: do not run them again. ${reviewerTask}`;\n\nexport const reviewerInstructionsWithoutCommands = `You are the reviewer agent. Nobody has run the tests or checks: the executor had no command for them. Judge from the code whether the tests it added or changed would pass, and do not approve a change whose tests could not. ${reviewerTask}`;\n\nconst getPlannerPrompt = (userPrompt: string, previousFailureReason?: string) => {\n\tconst feedback = previousFailureReason\n\t\t? `\\n\\nFeedback from the previous attempt:\\n---\\n${previousFailureReason}\\n---`\n\t\t: '';\n\n\treturn `Original user request:\n---\n${userPrompt}\n---${feedback}`;\n};\n\nconst getExecutorPrompt = (userPrompt: string, plannerPrompt: string) =>\n\t`Original user request:\n---\n${userPrompt}\n---\n\nCurrent implementation plan:\n---\n${plannerPrompt}\n---`;\n\nconst getReviewerPrompt = (\n\tuserPrompt: string,\n\tplannerPrompt: string,\n\texecutorResult: string,\n\tparseFailureReason?: string,\n) => {\n\tconst retryNotice = parseFailureReason\n\t\t? `\\n\\nYour previous response could not be used: ${parseFailureReason} Respond with JSON only, matching the schema exactly, with no surrounding text and no markdown code fences.`\n\t\t: '';\n\n\treturn `Original user request:\n---\n${userPrompt}\n---\n\nPlanner's current plan:\n---\n${plannerPrompt}\n---\n\nExecutor response (context only; verify it independently):\n---\n${executorResult}\n---${retryNotice}`;\n};\n\ninterface RunTotals {\n\ttokens: Tokens | undefined;\n\t/** A role answered without usage, so the run's total cannot be known. */\n\tunreported: boolean;\n}\n\n// Every role's response counts, even an empty one that starts another round: it was billed.\nfunction addToTotals(totals: RunTotals, response: AgentResponse): void {\n\tif (!response.tokens) totals.unreported = true;\n\ttotals.tokens = addTokens(totals.tokens, response.tokens);\n}\n\n/** Unknown rather than partial: a sum missing a billed call must not look complete. */\nfunction runTokens(totals: RunTotals): Tokens | undefined {\n\treturn totals.unreported ? undefined : totals.tokens;\n}\n\nfunction stripCodeFence(text: string): string {\n\tconst match = /^```(?:json)?\\s*([\\s\\S]*?)\\s*```$/.exec(text.trim());\n\treturn match?.[1] ?? text;\n}\n\nfunction parseReviewerDecision(\n\tresponse: AgentResponse,\n\treviewerDecisionValidator: Validator<ReviewerDecision>,\n): ReviewerDecision {\n\tif (!response.response) {\n\t\tthrow new RecoverableError(\"There's no decision from the reviewer, something went wrong\", {\n\t\t\tcause: 'Reviewer decision is missing.',\n\t\t});\n\t}\n\n\tlet parsedResponse: unknown;\n\ttry {\n\t\tparsedResponse = JSON.parse(stripCodeFence(response.response));\n\t} catch {\n\t\tthrow new RecoverableError('Reviewer returned an invalid decision', {\n\t\t\tcause: 'Reviewer response must be valid JSON.',\n\t\t});\n\t}\n\n\tconst decision = reviewerDecisionValidator.validate(parsedResponse);\n\tif (!decision) {\n\t\tthrow new RecoverableError('Reviewer returned an invalid decision', {\n\t\t\tcause: 'Reviewer response did not match the decision schema.',\n\t\t});\n\t}\n\n\treturn decision;\n}\n\nexport class OrchestratorAgent implements Agent {\n\tprivate plannerAgent: Agent;\n\tprivate executorAgent: Agent;\n\tprivate reviewerAgent: Agent;\n\tprivate reviewerDecisionValidator: Validator<ReviewerDecision>;\n\tprivate maxAttempts: number;\n\tprivate logger: ILogger;\n\n\tconstructor({\n\t\tplannerAgent,\n\t\texecutorAgent,\n\t\treviewerAgent,\n\t\treviewerDecisionValidator,\n\t\tmaxAttempts = 3,\n\t\tlogger,\n\t}: {\n\t\tplannerAgent: Agent;\n\t\texecutorAgent: Agent;\n\t\treviewerAgent: Agent;\n\t\treviewerDecisionValidator: Validator<ReviewerDecision>;\n\t\tmaxAttempts?: number;\n\t\tlogger: ILogger;\n\t}) {\n\t\tif (!Number.isInteger(maxAttempts) || maxAttempts < 1) {\n\t\t\tthrow new RangeError('maxAttempts must be a positive integer');\n\t\t}\n\n\t\tthis.plannerAgent = plannerAgent;\n\t\tthis.executorAgent = executorAgent;\n\t\tthis.reviewerAgent = reviewerAgent;\n\t\tthis.reviewerDecisionValidator = reviewerDecisionValidator;\n\t\tthis.maxAttempts = maxAttempts;\n\t\tthis.logger = logger;\n\t}\n\n\t/**\n\t * Every role, round and retry of one run shares one run context, so what the executor writes\n\t * is one run of the workspace's history, and the planner's and reviewer's turns are tied to\n\t * it. The orchestrator ends that context with the run, unless an outer agent owns it.\n\t */\n\tasync run(prompt: string, options: RunOptions): Promise<AgentResponse> {\n\t\tconst start = Date.now();\n\t\t// Per invocation, not per instance: the CLI keeps one orchestrator for a whole session.\n\t\tconst totals: RunTotals = { tokens: undefined, unreported: false };\n\t\tconst inherited = runContextOf(options);\n\t\tconst context = inherited ?? new RunContext();\n\t\tlet end: RunEnd = 'failed';\n\n\t\ttry {\n\t\t\tawait this.runRounds(prompt, inherited ? options : withRunContext(options, context), totals);\n\t\t\tend = 'completed';\n\t\t} catch (error) {\n\t\t\tif (isAbortError(error)) end = 'cancelled';\n\t\t\t// A failing role carries its own tokens; the roles before it are in the totals.\n\t\t\tthrow withSpentTokens(error, totals.tokens, totals.unreported);\n\t\t} finally {\n\t\t\tif (!inherited) await this.finishContext(context, end);\n\t\t}\n\n\t\t// Read once the run ended: a run that changed nothing left the history then.\n\t\tconst { runId } = context.historyRun;\n\t\treturn {\n\t\t\tresponse: 'All job has finished',\n\t\t\ttokens: runTokens(totals),\n\t\t\t// The whole run's wall clock, so it matches what the consumer waited.\n\t\t\tduration: (Date.now() - start) / 1000,\n\t\t\t...(!inherited && runId ? { runId } : {}),\n\t\t};\n\t}\n\n\t/** The run's records end here, but its result stands: what it changed is already on disk. */\n\tprivate async finishContext(context: RunContext, end: RunEnd): Promise<void> {\n\t\ttry {\n\t\t\tawait context.finish(end);\n\t\t} catch (error) {\n\t\t\tthis.warn(`The run could not close its records: ${describeFailure(error)}`);\n\t\t}\n\t}\n\n\tprivate async runRounds(prompt: string, options: RunOptions, totals: RunTotals): Promise<void> {\n\t\tlet lastFailureReason: string | undefined;\n\n\t\tfor (let attempt = 1; attempt <= this.maxAttempts; attempt++) {\n\t\t\tconst isLastAttempt = attempt === this.maxAttempts;\n\n\t\t\tconst plannerResponse = await this.plannerAgent.run(\n\t\t\t\tgetPlannerPrompt(prompt, lastFailureReason),\n\t\t\t\tthis.optionsForRole(options, 'planner'),\n\t\t\t);\n\t\t\taddToTotals(totals, plannerResponse);\n\n\t\t\tif (!plannerResponse.response) {\n\t\t\t\tlastFailureReason = 'The planner produced no response.';\n\t\t\t\tif (isLastAttempt) {\n\t\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: lastFailureReason });\n\t\t\t\t}\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Attempt ${String(attempt)}/${String(this.maxAttempts)}: the planner produced no response; starting another round`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst executorResponse = await this.executorAgent.run(\n\t\t\t\tgetExecutorPrompt(prompt, plannerResponse.response),\n\t\t\t\tthis.optionsForRole(options, 'executor'),\n\t\t\t);\n\t\t\taddToTotals(totals, executorResponse);\n\n\t\t\tif (!executorResponse.response) {\n\t\t\t\tlastFailureReason = 'The executor produced no response.';\n\t\t\t\tif (isLastAttempt) {\n\t\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: lastFailureReason });\n\t\t\t\t}\n\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Attempt ${String(attempt)}/${String(this.maxAttempts)}: the executor produced no response; starting another round`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst reviewerDecision = await this.getReviewerDecision(\n\t\t\t\tprompt,\n\t\t\t\tplannerResponse.response,\n\t\t\t\texecutorResponse.response,\n\t\t\t\toptions,\n\t\t\t\ttotals,\n\t\t\t);\n\n\t\t\tif (reviewerDecision.decision === 'approved') return;\n\n\t\t\tlastFailureReason = reviewerDecision.feedback;\n\t\t\tif (isLastAttempt) {\n\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: lastFailureReason });\n\t\t\t}\n\n\t\t\tthis.warn(\n\t\t\t\t`Attempt ${String(attempt)}/${String(this.maxAttempts)}: the reviewer rejected the round; starting another with its feedback`,\n\t\t\t\tlastFailureReason,\n\t\t\t);\n\t\t}\n\n\t\tthrow new UnrecoverableError('Max attempts exhausted', {\n\t\t\tcause: lastFailureReason ?? 'Unknown failure.',\n\t\t});\n\t}\n\n\t// Retries only the reviewer call on a malformed decision, instead of redoing\n\t// planning/execution — a bad or unparsable reviewer response is not evidence\n\t// the plan or the implementation were wrong.\n\tprivate async getReviewerDecision(\n\t\tuserPrompt: string,\n\t\tplannerPrompt: string,\n\t\texecutorResult: string,\n\t\toptions: RunOptions,\n\t\ttotals: RunTotals,\n\t): Promise<ReviewerDecision> {\n\t\tlet parseFailureReason: string | undefined;\n\n\t\tfor (let attempt = 1; attempt <= this.maxAttempts; attempt++) {\n\t\t\tconst reviewerResponse = await this.reviewerAgent.run(\n\t\t\t\tgetReviewerPrompt(userPrompt, plannerPrompt, executorResult, parseFailureReason),\n\t\t\t\tthis.optionsForRole(options, 'reviewer'),\n\t\t\t);\n\n\t\t\taddToTotals(totals, reviewerResponse);\n\n\t\t\ttry {\n\t\t\t\treturn parseReviewerDecision(reviewerResponse, this.reviewerDecisionValidator);\n\t\t\t} catch (error) {\n\t\t\t\tif (!(error instanceof RecoverableError)) throw error;\n\n\t\t\t\tparseFailureReason = error.cause;\n\t\t\t\tif (attempt === this.maxAttempts) {\n\t\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: parseFailureReason });\n\t\t\t\t}\n\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Reviewer decision ${String(attempt)}/${String(this.maxAttempts)} was unusable; asking the reviewer again`,\n\t\t\t\t\tparseFailureReason,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tthrow new UnrecoverableError('Max attempts exhausted', {\n\t\t\tcause: parseFailureReason ?? 'Unknown failure.',\n\t\t});\n\t}\n\n\tprivate warn(...args: unknown[]): void {\n\t\ttreatErrors(\n\t\t\t() => {\n\t\t\t\tthis.logger.warn(...args);\n\t\t\t},\n\t\t\tclassifyHostFailure,\n\t\t\t'Orchestrator logger failed while reporting a retry',\n\t\t);\n\t}\n\n\t/**\n\t * Keep the options object extensible and the private run context intact while making the\n\t * orchestrator's attribution authoritative over anything a nested agent supplied.\n\t */\n\tprivate optionsForRole(options: RunOptions, agent: ProgressAgent): RunOptions {\n\t\tconst onProgress = options.onProgress;\n\t\treturn {\n\t\t\t...options,\n\t\t\tonProgress: onProgress\n\t\t\t\t? (event: ProgressEvent) => {\n\t\t\t\t\t\ttreatErrors(\n\t\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\t\tonProgress({ ...event, agent });\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tclassifyHostFailure,\n\t\t\t\t\t\t\t'Orchestrator progress callback failed',\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t: undefined,\n\t\t};\n\t}\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Validator } from '
|
|
1
|
+
import type { Validator } from '#src/shared/domain/validator';
|
|
2
2
|
import type { ReviewerDecision } from '../../domain/model/reviewerDecision.ts';
|
|
3
3
|
export declare class ReviewerDecisionValidator implements Validator<ReviewerDecision> {
|
|
4
4
|
validate(input: unknown): ReviewerDecision | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reviewerDecisionValidator.js","sourceRoot":"","sources":["../../../../src/orchestration/infrastructure/model/reviewerDecisionValidator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,MAAM,sBAAsB,GAAG,CAAC,CAAC,kBAAkB,CAAC,UAAU,EAAE;IAC/D,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;IAC7C,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;CACjF,CAAC,CAAC;AAEH,MAAM,OAAO,yBAAyB;IACrC,QAAQ,CAAC,KAAc;QACtB,MAAM,MAAM,GAAG,sBAAsB,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACvD,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IACjD,CAAC;CACD","sourcesContent":["import { z } from 'zod';\nimport type { Validator } from '
|
|
1
|
+
{"version":3,"file":"reviewerDecisionValidator.js","sourceRoot":"","sources":["../../../../src/orchestration/infrastructure/model/reviewerDecisionValidator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,MAAM,sBAAsB,GAAG,CAAC,CAAC,kBAAkB,CAAC,UAAU,EAAE;IAC/D,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;IAC7C,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;CACjF,CAAC,CAAC;AAEH,MAAM,OAAO,yBAAyB;IACrC,QAAQ,CAAC,KAAc;QACtB,MAAM,MAAM,GAAG,sBAAsB,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACvD,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IACjD,CAAC;CACD","sourcesContent":["import { z } from 'zod';\nimport type { Validator } from '#src/shared/domain/validator';\nimport type { ReviewerDecision } from '../../domain/model/reviewerDecision.ts';\n\nconst reviewerDecisionSchema = z.discriminatedUnion('decision', [\n\tz.object({ decision: z.literal('approved') }),\n\tz.object({ decision: z.literal('rejected'), feedback: z.string().trim().min(1) }),\n]);\n\nexport class ReviewerDecisionValidator implements Validator<ReviewerDecision> {\n\tvalidate(input: unknown): ReviewerDecision | undefined {\n\t\tconst result = reviewerDecisionSchema.safeParse(input);\n\t\treturn result.success ? result.data : undefined;\n\t}\n}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
+
/** A deliberate stop: an `AbortError`, or the `TimeoutError` that `AbortSignal.timeout` aborts with. */
|
|
1
2
|
export function isAbortError(e) {
|
|
2
|
-
return e instanceof Error && e.name === 'AbortError';
|
|
3
|
+
return e instanceof Error && (e.name === 'AbortError' || e.name === 'TimeoutError');
|
|
3
4
|
}
|
|
4
5
|
//# sourceMappingURL=isAbortError.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"isAbortError.js","sourceRoot":"","sources":["../../../src/shared/domain/isAbortError.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,YAAY,CAAC,CAAU;IACtC,OAAO,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,IAAI,KAAK,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"isAbortError.js","sourceRoot":"","sources":["../../../src/shared/domain/isAbortError.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,MAAM,UAAU,YAAY,CAAC,CAAU;IACtC,OAAO,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,YAAY,IAAI,CAAC,CAAC,IAAI,KAAK,cAAc,CAAC,CAAC;AACrF,CAAC","sourcesContent":["/** A deliberate stop: an `AbortError`, or the `TimeoutError` that `AbortSignal.timeout` aborts with. */\nexport function isAbortError(e: unknown): e is Error {\n\treturn e instanceof Error && (e.name === 'AbortError' || e.name === 'TimeoutError');\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.js","sourceRoot":"","sources":["../../../src/shared/domain/validator.ts"],"names":[],"mappings":"","sourcesContent":["/** Turns a model's parsed answer into the typed value a caller needs, or `undefined` when it does not fit. */\nexport interface Validator<T> {\n\tvalidate(input: unknown): T | undefined;\n}\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { Tool } from '../domain/tool.ts';
|
|
3
3
|
import type { Workspace } from '../domain/workspace.ts';
|
|
4
|
+
export declare const maxResultBytes: number;
|
|
4
5
|
/**
|
|
5
6
|
* Lines of a file as the model reads them: each numbered, a line too long cut, and a notice for
|
|
6
7
|
* what was cut or lies beyond.
|
|
@@ -5,7 +5,9 @@ import { cutLine, maxLineLength } from "../domain/lineLength.js";
|
|
|
5
5
|
const maxFiles = 200;
|
|
6
6
|
const maxMatches = 100;
|
|
7
7
|
const maxLines = 200;
|
|
8
|
-
//
|
|
8
|
+
// What one read or one search may bring into the context, whatever its lines: 200 lines of
|
|
9
|
+
// prose can be 50 KB, and the context carries them on every step after.
|
|
10
|
+
export const maxResultBytes = 16 * 1024;
|
|
9
11
|
// Strict mode has no optional fields: an argument the model may leave out is nullable.
|
|
10
12
|
const scope = {
|
|
11
13
|
path: z.string().nullable().describe('A folder or file to stay inside, relative to the root.'),
|
|
@@ -23,21 +25,39 @@ export function renderLines({ path, lines, from, totalLines, truncated, }) {
|
|
|
23
25
|
if (lines.length === 0) {
|
|
24
26
|
return `No lines from ${String(from)}: the file has ${String(totalLines)} lines.`;
|
|
25
27
|
}
|
|
26
|
-
const numbered = lines.map((text, index) => `${String(from + index)}: ${cutLine(path, text)}`);
|
|
28
|
+
const numbered = withinBytes(lines.map((text, index) => `${String(from + index)}: ${cutLine(path, text)}`));
|
|
29
|
+
const shown = lines.slice(0, numbered.length);
|
|
27
30
|
const max = maxLineLength(path);
|
|
28
|
-
const cut =
|
|
31
|
+
const cut = shown.filter(text => text.length > max).length;
|
|
32
|
+
const next = from + shown.length;
|
|
29
33
|
const notices = [
|
|
30
34
|
...(cut > 0
|
|
31
35
|
? [`[${String(cut)} lines were longer than ${String(max)} characters and were cut.]`]
|
|
32
36
|
: []),
|
|
33
|
-
...(
|
|
37
|
+
...(shown.length < lines.length
|
|
34
38
|
? [
|
|
35
|
-
`[The file has ${String(totalLines)} lines. Read from line ${String(
|
|
39
|
+
`[Lines ${String(from)} to ${String(next - 1)} fill the ${String(maxResultBytes / 1024)} KB one read may return. The file has ${String(totalLines)} lines. Read from line ${String(next)} to continue.]`,
|
|
36
40
|
]
|
|
37
|
-
:
|
|
41
|
+
: truncated
|
|
42
|
+
? [
|
|
43
|
+
`[The file has ${String(totalLines)} lines. Read from line ${String(next)} to continue.]`,
|
|
44
|
+
]
|
|
45
|
+
: []),
|
|
38
46
|
];
|
|
39
47
|
return [...numbered, ...notices].join('\n');
|
|
40
48
|
}
|
|
49
|
+
/** The first of `lines` that fit in `maxResultBytes` together, and always the first one. */
|
|
50
|
+
function withinBytes(lines) {
|
|
51
|
+
let bytes = 0;
|
|
52
|
+
const kept = [];
|
|
53
|
+
for (const line of lines) {
|
|
54
|
+
bytes += Buffer.byteLength(line, 'utf8') + 1;
|
|
55
|
+
if (bytes > maxResultBytes && kept.length > 0)
|
|
56
|
+
break;
|
|
57
|
+
kept.push(line);
|
|
58
|
+
}
|
|
59
|
+
return kept;
|
|
60
|
+
}
|
|
41
61
|
/** What `readFile` takes, shared with the harness's own version of it. */
|
|
42
62
|
export const readFileDescription = 'Reads lines of a file, each prefixed with its number.';
|
|
43
63
|
export const readFileInput = z.object({
|
|
@@ -117,10 +137,12 @@ export function createWorkspaceTools(workspace) {
|
|
|
117
137
|
if (matches.length === 0) {
|
|
118
138
|
return 'No matches.';
|
|
119
139
|
}
|
|
120
|
-
const results = matches.map(match => `${match.path}:${String(match.line)}: ${cutLine(match.path, match.text)}`);
|
|
121
|
-
const notice =
|
|
122
|
-
? `\n[Showing ${String(results.length)} of ${String(total)} matches. Narrow the path or glob to see the rest.]`
|
|
123
|
-
:
|
|
140
|
+
const results = withinBytes(matches.map(match => `${match.path}:${String(match.line)}: ${cutLine(match.path, match.text)}`));
|
|
141
|
+
const notice = results.length < matches.length
|
|
142
|
+
? `\n[Showing ${String(results.length)} of ${String(total)} matches, the ${String(maxResultBytes / 1024)} KB one search may return. Narrow the pattern, the path or the glob to see the rest.]`
|
|
143
|
+
: truncated
|
|
144
|
+
? `\n[Showing ${String(results.length)} of ${String(total)} matches. Narrow the path or glob to see the rest.]`
|
|
145
|
+
: '';
|
|
124
146
|
return results.join('\n') + notice;
|
|
125
147
|
},
|
|
126
148
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspaceTools.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/workspaceTools.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod';\nimport type { Tool } from '../domain/tool.ts';\nimport type { Workspace } from '../domain/workspace.ts';\nimport { defineTool } from './defineTool.ts';\nimport { cutLine, maxLineLength } from '../domain/lineLength.ts';\n\n// What one call may return. The model can ask for fewer lines, never for more.\nconst maxFiles = 200;\nconst maxMatches = 100;\nconst maxLines = 200;\n// One minified line can be larger than everything else the model has read.\n\n// Strict mode has no optional fields: an argument the model may leave out is nullable.\nconst scope = {\n\tpath: z.string().nullable().describe('A folder or file to stay inside, relative to the root.'),\n\tglob: z\n\t\t.string()\n\t\t.nullable()\n\t\t.describe(\n\t\t\t'Only paths matching this glob, relative to the root. \"*\" stays inside one folder and \"**\" crosses folders: \"**/*.ts\" is every TypeScript file, \"*.ts\" only those at the top.',\n\t\t),\n};\n\n/**\n * Lines of a file as the model reads them: each numbered, a line too long cut, and a notice for\n * what was cut or lies beyond.\n */\nexport function renderLines({\n\tpath,\n\tlines,\n\tfrom,\n\ttotalLines,\n\ttruncated,\n}: {\n\t/** The file, which decides how long a line may be. */\n\tpath: string;\n\tlines: string[];\n\tfrom: number;\n\ttotalLines: number;\n\ttruncated: boolean;\n}): string {\n\t// The file's length tells the model where its range went wrong.\n\tif (lines.length === 0) {\n\t\treturn `No lines from ${String(from)}: the file has ${String(totalLines)} lines.`;\n\t}\n\n\tconst numbered = lines.map((text, index) => `${String(from + index)}: ${cutLine(path, text)}`);\n\tconst max = maxLineLength(path);\n\tconst cut = lines.filter(text => text.length > max).length;\n\tconst notices = [\n\t\t...(cut > 0\n\t\t\t? [`[${String(cut)} lines were longer than ${String(max)} characters and were cut.]`]\n\t\t\t: []),\n\t\t...(truncated\n\t\t\t? [\n\t\t\t\t\t`[The file has ${String(totalLines)} lines. Read from line ${String(from + lines.length)} to continue.]`,\n\t\t\t\t]\n\t\t\t: []),\n\t];\n\treturn [...numbered, ...notices].join('\\n');\n}\n\n/** What `readFile` takes, shared with the harness's own version of it. */\nexport const readFileDescription = 'Reads lines of a file, each prefixed with its number.';\nexport const readFileInput = z.object({\n\tpath: z.string().describe('The file, relative to the root.'),\n\tfromLine: z.number().nullable().describe('The first line to read, from 1. Defaults to 1.'),\n\tlineCount: z\n\t\t.number()\n\t\t.nullable()\n\t\t.describe(\n\t\t\t`How many lines to read, at most ${String(maxLines)}. Defaults to ${String(maxLines)}.`,\n\t\t),\n});\n\n/** The range a `readFile` call asks for, checked: strict mode allows the schema no minimum. */\nexport function readRange({\n\tfromLine,\n\tlineCount,\n}: {\n\tfromLine: number | null;\n\tlineCount: number | null;\n}): { from: number; count: number } {\n\tconst from = fromLine ?? 1;\n\tconst requested = lineCount ?? maxLines;\n\tassertPositiveInteger('fromLine', from);\n\tassertPositiveInteger('lineCount', requested);\n\treturn { from, count: Math.min(requested, maxLines) };\n}\n\n/** A stable description of a read request, using only the range the model supplied. */\nexport function readFileSummary({\n\tpath,\n\tfromLine,\n\tlineCount,\n}: {\n\tpath: string;\n\tfromLine: number | null;\n\tlineCount: number | null;\n}): string {\n\tconst suppliedFrom = fromLine ?? 1;\n\tconst from = Number.isInteger(suppliedFrom) && suppliedFrom > 0 ? suppliedFrom : 1;\n\tconst suppliedCount = lineCount ?? maxLines;\n\tconst requested = Number.isInteger(suppliedCount) && suppliedCount > 0 ? suppliedCount : maxLines;\n\tconst count = Math.min(requested, maxLines);\n\treturn `${path}:${String(from)}-${String(from + count - 1)}`;\n}\n\nfunction scopeSummary(path: string | null, glob: string | null): string {\n\tconst scopePath = path ?? '.';\n\treturn glob ? `${scopePath === '.' ? '' : `${scopePath}/`}${glob}` : scopePath;\n}\n\nfunction assertPositiveInteger(name: string, value: number): void {\n\tif (!Number.isInteger(value) || value < 1) {\n\t\tthrow new Error(`${name} must be a whole number of at least 1; got ${String(value)}`);\n\t}\n}\n\n/** The three read-only tools an agent explores a repository with. */\nexport function createWorkspaceTools(workspace: Workspace): Tool[] {\n\treturn [\n\t\tdefineTool({\n\t\t\tname: 'listFiles',\n\t\t\tdescription:\n\t\t\t\t'Lists the files of the repository, sorted. Ignored files such as dependencies are left out.',\n\t\t\trisk: 'safe',\n\t\t\tinput: z.object(scope),\n\t\t\tsummarize: ({ path, glob }) => scopeSummary(path, glob),\n\t\t\texecute: async ({ path, glob }, signal) => {\n\t\t\t\t// The port takes \"no scope\" as a missing field.\n\t\t\t\tconst { files, total, truncated } = await workspace.listFiles(\n\t\t\t\t\t{ path: path ?? undefined, glob: glob ?? undefined, limit: maxFiles },\n\t\t\t\t\tsignal,\n\t\t\t\t);\n\n\t\t\t\t// Nothing found is an answer too, not an empty string.\n\t\t\t\tif (files.length === 0) {\n\t\t\t\t\treturn 'No files.';\n\t\t\t\t}\n\n\t\t\t\tconst notice = truncated\n\t\t\t\t\t? `\\n[Showing ${String(files.length)} of ${String(total)} files. Narrow the path or glob to see the rest.]`\n\t\t\t\t\t: '';\n\t\t\t\treturn files.join('\\n') + notice;\n\t\t\t},\n\t\t}),\n\n\t\tdefineTool({\n\t\t\tname: 'searchText',\n\t\t\tdescription:\n\t\t\t\t'Searches the repository for a regular expression and returns each matching line as path:line: text.',\n\t\t\trisk: 'safe',\n\t\t\tinput: z.object({\n\t\t\t\tpattern: z.string().describe('A regular expression.'),\n\t\t\t\tignoreCase: z.boolean().nullable().describe('Match regardless of case. Defaults to false.'),\n\t\t\t\t...scope,\n\t\t\t}),\n\t\t\tsummarize: ({ path, glob, pattern }) => `\"${pattern}\" in ${scopeSummary(path, glob)}`,\n\t\t\texecute: async ({ path, glob, ignoreCase, pattern }, signal) => {\n\t\t\t\tconst { matches, total, truncated } = await workspace.searchText(\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: path ?? undefined,\n\t\t\t\t\t\tglob: glob ?? undefined,\n\t\t\t\t\t\tignoreCase: ignoreCase ?? false,\n\t\t\t\t\t\tpattern,\n\t\t\t\t\t\tlimit: maxMatches,\n\t\t\t\t\t},\n\t\t\t\t\tsignal,\n\t\t\t\t);\n\n\t\t\t\tif (matches.length === 0) {\n\t\t\t\t\treturn 'No matches.';\n\t\t\t\t}\n\n\t\t\t\tconst results = matches.map(\n\t\t\t\t\tmatch => `${match.path}:${String(match.line)}: ${cutLine(match.path, match.text)}`,\n\t\t\t\t);\n\n\t\t\t\tconst notice = truncated\n\t\t\t\t\t? `\\n[Showing ${String(results.length)} of ${String(total)} matches. Narrow the path or glob to see the rest.]`\n\t\t\t\t\t: '';\n\t\t\t\treturn results.join('\\n') + notice;\n\t\t\t},\n\t\t}),\n\n\t\tdefineTool({\n\t\t\tname: 'readFile',\n\t\t\tdescription: readFileDescription,\n\t\t\trisk: 'safe',\n\t\t\tinput: readFileInput,\n\t\t\tsummarize: readFileSummary,\n\t\t\texecute: async ({ fromLine, lineCount, path }, signal) => {\n\t\t\t\t// Checked before reading.\n\t\t\t\tconst { from, count } = readRange({ fromLine, lineCount });\n\t\t\t\tconst { lines, totalLines, truncated } = await workspace.readFile(\n\t\t\t\t\t{ path, fromLine: from, lineCount: count },\n\t\t\t\t\tsignal,\n\t\t\t\t);\n\t\t\t\treturn renderLines({ path, lines, from, totalLines, truncated });\n\t\t\t},\n\t\t}),\n\t];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"workspaceTools.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/workspaceTools.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod';\nimport type { Tool } from '../domain/tool.ts';\nimport type { Workspace } from '../domain/workspace.ts';\nimport { defineTool } from './defineTool.ts';\nimport { cutLine, maxLineLength } from '../domain/lineLength.ts';\n\n// What one call may return. The model can ask for fewer lines, never for more.\nconst maxFiles = 200;\nconst maxMatches = 100;\nconst maxLines = 200;\n// What one read or one search may bring into the context, whatever its lines: 200 lines of\n// prose can be 50 KB, and the context carries them on every step after.\nexport const maxResultBytes = 16 * 1024;\n\n// Strict mode has no optional fields: an argument the model may leave out is nullable.\nconst scope = {\n\tpath: z.string().nullable().describe('A folder or file to stay inside, relative to the root.'),\n\tglob: z\n\t\t.string()\n\t\t.nullable()\n\t\t.describe(\n\t\t\t'Only paths matching this glob, relative to the root. \"*\" stays inside one folder and \"**\" crosses folders: \"**/*.ts\" is every TypeScript file, \"*.ts\" only those at the top.',\n\t\t),\n};\n\n/**\n * Lines of a file as the model reads them: each numbered, a line too long cut, and a notice for\n * what was cut or lies beyond.\n */\nexport function renderLines({\n\tpath,\n\tlines,\n\tfrom,\n\ttotalLines,\n\ttruncated,\n}: {\n\t/** The file, which decides how long a line may be. */\n\tpath: string;\n\tlines: string[];\n\tfrom: number;\n\ttotalLines: number;\n\ttruncated: boolean;\n}): string {\n\t// The file's length tells the model where its range went wrong.\n\tif (lines.length === 0) {\n\t\treturn `No lines from ${String(from)}: the file has ${String(totalLines)} lines.`;\n\t}\n\n\tconst numbered = withinBytes(\n\t\tlines.map((text, index) => `${String(from + index)}: ${cutLine(path, text)}`),\n\t);\n\tconst shown = lines.slice(0, numbered.length);\n\tconst max = maxLineLength(path);\n\tconst cut = shown.filter(text => text.length > max).length;\n\tconst next = from + shown.length;\n\tconst notices = [\n\t\t...(cut > 0\n\t\t\t? [`[${String(cut)} lines were longer than ${String(max)} characters and were cut.]`]\n\t\t\t: []),\n\t\t...(shown.length < lines.length\n\t\t\t? [\n\t\t\t\t\t`[Lines ${String(from)} to ${String(next - 1)} fill the ${String(maxResultBytes / 1024)} KB one read may return. The file has ${String(totalLines)} lines. Read from line ${String(next)} to continue.]`,\n\t\t\t\t]\n\t\t\t: truncated\n\t\t\t\t? [\n\t\t\t\t\t\t`[The file has ${String(totalLines)} lines. Read from line ${String(next)} to continue.]`,\n\t\t\t\t\t]\n\t\t\t\t: []),\n\t];\n\treturn [...numbered, ...notices].join('\\n');\n}\n\n/** The first of `lines` that fit in `maxResultBytes` together, and always the first one. */\nfunction withinBytes(lines: string[]): string[] {\n\tlet bytes = 0;\n\tconst kept: string[] = [];\n\tfor (const line of lines) {\n\t\tbytes += Buffer.byteLength(line, 'utf8') + 1;\n\t\tif (bytes > maxResultBytes && kept.length > 0) break;\n\t\tkept.push(line);\n\t}\n\treturn kept;\n}\n\n/** What `readFile` takes, shared with the harness's own version of it. */\nexport const readFileDescription = 'Reads lines of a file, each prefixed with its number.';\nexport const readFileInput = z.object({\n\tpath: z.string().describe('The file, relative to the root.'),\n\tfromLine: z.number().nullable().describe('The first line to read, from 1. Defaults to 1.'),\n\tlineCount: z\n\t\t.number()\n\t\t.nullable()\n\t\t.describe(\n\t\t\t`How many lines to read, at most ${String(maxLines)}. Defaults to ${String(maxLines)}.`,\n\t\t),\n});\n\n/** The range a `readFile` call asks for, checked: strict mode allows the schema no minimum. */\nexport function readRange({\n\tfromLine,\n\tlineCount,\n}: {\n\tfromLine: number | null;\n\tlineCount: number | null;\n}): { from: number; count: number } {\n\tconst from = fromLine ?? 1;\n\tconst requested = lineCount ?? maxLines;\n\tassertPositiveInteger('fromLine', from);\n\tassertPositiveInteger('lineCount', requested);\n\treturn { from, count: Math.min(requested, maxLines) };\n}\n\n/** A stable description of a read request, using only the range the model supplied. */\nexport function readFileSummary({\n\tpath,\n\tfromLine,\n\tlineCount,\n}: {\n\tpath: string;\n\tfromLine: number | null;\n\tlineCount: number | null;\n}): string {\n\tconst suppliedFrom = fromLine ?? 1;\n\tconst from = Number.isInteger(suppliedFrom) && suppliedFrom > 0 ? suppliedFrom : 1;\n\tconst suppliedCount = lineCount ?? maxLines;\n\tconst requested = Number.isInteger(suppliedCount) && suppliedCount > 0 ? suppliedCount : maxLines;\n\tconst count = Math.min(requested, maxLines);\n\treturn `${path}:${String(from)}-${String(from + count - 1)}`;\n}\n\nfunction scopeSummary(path: string | null, glob: string | null): string {\n\tconst scopePath = path ?? '.';\n\treturn glob ? `${scopePath === '.' ? '' : `${scopePath}/`}${glob}` : scopePath;\n}\n\nfunction assertPositiveInteger(name: string, value: number): void {\n\tif (!Number.isInteger(value) || value < 1) {\n\t\tthrow new Error(`${name} must be a whole number of at least 1; got ${String(value)}`);\n\t}\n}\n\n/** The three read-only tools an agent explores a repository with. */\nexport function createWorkspaceTools(workspace: Workspace): Tool[] {\n\treturn [\n\t\tdefineTool({\n\t\t\tname: 'listFiles',\n\t\t\tdescription:\n\t\t\t\t'Lists the files of the repository, sorted. Ignored files such as dependencies are left out.',\n\t\t\trisk: 'safe',\n\t\t\tinput: z.object(scope),\n\t\t\tsummarize: ({ path, glob }) => scopeSummary(path, glob),\n\t\t\texecute: async ({ path, glob }, signal) => {\n\t\t\t\t// The port takes \"no scope\" as a missing field.\n\t\t\t\tconst { files, total, truncated } = await workspace.listFiles(\n\t\t\t\t\t{ path: path ?? undefined, glob: glob ?? undefined, limit: maxFiles },\n\t\t\t\t\tsignal,\n\t\t\t\t);\n\n\t\t\t\t// Nothing found is an answer too, not an empty string.\n\t\t\t\tif (files.length === 0) {\n\t\t\t\t\treturn 'No files.';\n\t\t\t\t}\n\n\t\t\t\tconst notice = truncated\n\t\t\t\t\t? `\\n[Showing ${String(files.length)} of ${String(total)} files. Narrow the path or glob to see the rest.]`\n\t\t\t\t\t: '';\n\t\t\t\treturn files.join('\\n') + notice;\n\t\t\t},\n\t\t}),\n\n\t\tdefineTool({\n\t\t\tname: 'searchText',\n\t\t\tdescription:\n\t\t\t\t'Searches the repository for a regular expression and returns each matching line as path:line: text.',\n\t\t\trisk: 'safe',\n\t\t\tinput: z.object({\n\t\t\t\tpattern: z.string().describe('A regular expression.'),\n\t\t\t\tignoreCase: z.boolean().nullable().describe('Match regardless of case. Defaults to false.'),\n\t\t\t\t...scope,\n\t\t\t}),\n\t\t\tsummarize: ({ path, glob, pattern }) => `\"${pattern}\" in ${scopeSummary(path, glob)}`,\n\t\t\texecute: async ({ path, glob, ignoreCase, pattern }, signal) => {\n\t\t\t\tconst { matches, total, truncated } = await workspace.searchText(\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: path ?? undefined,\n\t\t\t\t\t\tglob: glob ?? undefined,\n\t\t\t\t\t\tignoreCase: ignoreCase ?? false,\n\t\t\t\t\t\tpattern,\n\t\t\t\t\t\tlimit: maxMatches,\n\t\t\t\t\t},\n\t\t\t\t\tsignal,\n\t\t\t\t);\n\n\t\t\t\tif (matches.length === 0) {\n\t\t\t\t\treturn 'No matches.';\n\t\t\t\t}\n\n\t\t\t\tconst results = withinBytes(\n\t\t\t\t\tmatches.map(\n\t\t\t\t\t\tmatch => `${match.path}:${String(match.line)}: ${cutLine(match.path, match.text)}`,\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\t\tconst notice =\n\t\t\t\t\tresults.length < matches.length\n\t\t\t\t\t\t? `\\n[Showing ${String(results.length)} of ${String(total)} matches, the ${String(maxResultBytes / 1024)} KB one search may return. Narrow the pattern, the path or the glob to see the rest.]`\n\t\t\t\t\t\t: truncated\n\t\t\t\t\t\t\t? `\\n[Showing ${String(results.length)} of ${String(total)} matches. Narrow the path or glob to see the rest.]`\n\t\t\t\t\t\t\t: '';\n\t\t\t\treturn results.join('\\n') + notice;\n\t\t\t},\n\t\t}),\n\n\t\tdefineTool({\n\t\t\tname: 'readFile',\n\t\t\tdescription: readFileDescription,\n\t\t\trisk: 'safe',\n\t\t\tinput: readFileInput,\n\t\t\tsummarize: readFileSummary,\n\t\t\texecute: async ({ fromLine, lineCount, path }, signal) => {\n\t\t\t\t// Checked before reading.\n\t\t\t\tconst { from, count } = readRange({ fromLine, lineCount });\n\t\t\t\tconst { lines, totalLines, truncated } = await workspace.readFile(\n\t\t\t\t\t{ path, fromLine: from, lineCount: count },\n\t\t\t\t\tsignal,\n\t\t\t\t);\n\t\t\t\treturn renderLines({ path, lines, from, totalLines, truncated });\n\t\t\t},\n\t\t}),\n\t];\n}\n"]}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { Agent, RunOptions } from '#src/agent/domain/agent';
|
|
2
|
+
import type { ILogger } from '#src/shared/domain/logger';
|
|
3
|
+
import type { Tokens } from '#src/shared/domain/tokens';
|
|
4
|
+
import type { Validator } from '#src/shared/domain/validator';
|
|
5
|
+
/** The names of a workflow's stages: the keys of its map of what each stage receives. */
|
|
6
|
+
export type WorkflowStageName<Inputs> = keyof Inputs & string;
|
|
7
|
+
/**
|
|
8
|
+
* What a stage chose once its answer was valid. `go` runs the stage it names, which receives
|
|
9
|
+
* `input` and nothing else; `stop` ends the run early on purpose and `complete` ends it finished,
|
|
10
|
+
* each with the run's result. `Inputs` maps each stage's name to what it receives, so a `go` that
|
|
11
|
+
* names a stage that does not exist, or hands it the wrong input, does not compile.
|
|
12
|
+
*/
|
|
13
|
+
export type WorkflowTransition<Inputs, Result> = {
|
|
14
|
+
[Name in WorkflowStageName<Inputs>]: {
|
|
15
|
+
type: 'go';
|
|
16
|
+
stage: Name;
|
|
17
|
+
input: Inputs[Name];
|
|
18
|
+
};
|
|
19
|
+
}[WorkflowStageName<Inputs>] | {
|
|
20
|
+
type: 'stop';
|
|
21
|
+
result: Result;
|
|
22
|
+
} | {
|
|
23
|
+
type: 'complete';
|
|
24
|
+
result: Result;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* One step of a workflow: how it creates its agent, the prompt that agent gets from the stage's
|
|
28
|
+
* input, how its JSON answer is validated, and the transition the valid answer selects.
|
|
29
|
+
*/
|
|
30
|
+
export interface WorkflowStageDefinition<Inputs, Name extends WorkflowStageName<Inputs>, Output, Result> {
|
|
31
|
+
/**
|
|
32
|
+
* Creates the stage's agent, with a context of its own, the first time the stage runs in a
|
|
33
|
+
* run. When the run comes back to the stage, the same agent goes on with its conversation;
|
|
34
|
+
* the next run creates another. It may be async, as `createLLMOrchestrator` is.
|
|
35
|
+
*/
|
|
36
|
+
agent(): Agent | PromiseLike<Agent>;
|
|
37
|
+
prompt(input: Inputs[Name]): string | PromiseLike<string>;
|
|
38
|
+
/** Receives the agent's answer parsed as JSON. */
|
|
39
|
+
readonly validator: Validator<Output>;
|
|
40
|
+
/** Receives the validated answer and the stage's own input, to carry forward what the next stage needs. */
|
|
41
|
+
transition(output: Output, input: Inputs[Name]): WorkflowTransition<Inputs, Result> | PromiseLike<WorkflowTransition<Inputs, Result>>;
|
|
42
|
+
/** How many answers the stage may give before an invalid one fails the run. */
|
|
43
|
+
readonly maxAttempts?: number;
|
|
44
|
+
}
|
|
45
|
+
/** A stage bound to its name, as `defineWorkflowStages` returns it. */
|
|
46
|
+
export interface WorkflowStage<Inputs, Result, Name extends WorkflowStageName<Inputs> = WorkflowStageName<Inputs>> extends WorkflowStageDefinition<Inputs, Name, unknown, Result> {
|
|
47
|
+
readonly name: Name;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Binds stage definitions to one workflow's map of stage inputs and its result. The function it
|
|
51
|
+
* returns infers each stage's output from its validator.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* const stage = defineWorkflowStages<{ triage: string; draft: Brief }, string>();
|
|
55
|
+
* const triage = stage('triage', { agent, prompt, validator, transition });
|
|
56
|
+
*/
|
|
57
|
+
export declare function defineWorkflowStages<Inputs, Result>(): <Name extends WorkflowStageName<Inputs>, Output>(name: Name, definition: WorkflowStageDefinition<Inputs, Name, Output, Result>) => WorkflowStage<Inputs, Result, Name>;
|
|
58
|
+
export interface WorkflowStageReport {
|
|
59
|
+
stage: string;
|
|
60
|
+
/** What the stage received. */
|
|
61
|
+
input: unknown;
|
|
62
|
+
/** The stage's validated answer. */
|
|
63
|
+
output: unknown;
|
|
64
|
+
/** How many answers the stage's agent gave, the last one valid. */
|
|
65
|
+
attempts: number;
|
|
66
|
+
/** Seconds. */
|
|
67
|
+
duration: number;
|
|
68
|
+
/** Absent when a provider did not report an answer's usage. */
|
|
69
|
+
tokens?: Tokens;
|
|
70
|
+
transition: 'go' | 'stop' | 'complete';
|
|
71
|
+
/** The stage that ran next, absent after `stop` or `complete`. */
|
|
72
|
+
nextStage?: string;
|
|
73
|
+
}
|
|
74
|
+
export interface WorkflowResult<Result> {
|
|
75
|
+
/** `stopped` is a deliberate early end, not a failure: a failure rejects the run. */
|
|
76
|
+
status: 'completed' | 'stopped';
|
|
77
|
+
result: Result;
|
|
78
|
+
/** Every stage's usage. Absent when any answer's usage was not reported. */
|
|
79
|
+
tokens?: Tokens;
|
|
80
|
+
/** Seconds. */
|
|
81
|
+
duration: number;
|
|
82
|
+
runId?: string;
|
|
83
|
+
/** One report per stage run, in order: what each stage received, answered and chose. */
|
|
84
|
+
stages: readonly WorkflowStageReport[];
|
|
85
|
+
}
|
|
86
|
+
export interface Workflow<Input, Result> {
|
|
87
|
+
run(input: Input, options: RunOptions): Promise<WorkflowResult<Result>>;
|
|
88
|
+
}
|
|
89
|
+
export interface CreateWorkflowOptions<Inputs, Result, Initial extends WorkflowStageName<Inputs> = WorkflowStageName<Inputs>> {
|
|
90
|
+
initialStage: Initial;
|
|
91
|
+
/** Every stage, under its own name. */
|
|
92
|
+
stages: {
|
|
93
|
+
readonly [Name in WorkflowStageName<Inputs>]: WorkflowStage<Inputs, Result, Name>;
|
|
94
|
+
} & Readonly<Record<string, WorkflowStage<Inputs, Result>>>;
|
|
95
|
+
/** The default for a stage without its own. Defaults to 3. */
|
|
96
|
+
maxAttempts?: number;
|
|
97
|
+
/** How many times a run may `go` to a stage, so a loop of stages ends. Defaults to 100. */
|
|
98
|
+
maxTransitions?: number;
|
|
99
|
+
logger?: ILogger;
|
|
100
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binds stage definitions to one workflow's map of stage inputs and its result. The function it
|
|
3
|
+
* returns infers each stage's output from its validator.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* const stage = defineWorkflowStages<{ triage: string; draft: Brief }, string>();
|
|
7
|
+
* const triage = stage('triage', { agent, prompt, validator, transition });
|
|
8
|
+
*/
|
|
9
|
+
export function defineWorkflowStages() {
|
|
10
|
+
return (name, definition) => ({ ...definition, name });
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=workflow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow.js","sourceRoot":"","sources":["../../../src/workflow/domain/workflow.ts"],"names":[],"mappings":"AA0DA;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB;IAOnC,OAAO,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;AACxD,CAAC","sourcesContent":["import type { Agent, RunOptions } from '#src/agent/domain/agent';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport type { Tokens } from '#src/shared/domain/tokens';\nimport type { Validator } from '#src/shared/domain/validator';\n\n/** The names of a workflow's stages: the keys of its map of what each stage receives. */\nexport type WorkflowStageName<Inputs> = keyof Inputs & string;\n\n/**\n * What a stage chose once its answer was valid. `go` runs the stage it names, which receives\n * `input` and nothing else; `stop` ends the run early on purpose and `complete` ends it finished,\n * each with the run's result. `Inputs` maps each stage's name to what it receives, so a `go` that\n * names a stage that does not exist, or hands it the wrong input, does not compile.\n */\nexport type WorkflowTransition<Inputs, Result> =\n\t| {\n\t\t\t[Name in WorkflowStageName<Inputs>]: { type: 'go'; stage: Name; input: Inputs[Name] };\n\t }[WorkflowStageName<Inputs>]\n\t| { type: 'stop'; result: Result }\n\t| { type: 'complete'; result: Result };\n\n/**\n * One step of a workflow: how it creates its agent, the prompt that agent gets from the stage's\n * input, how its JSON answer is validated, and the transition the valid answer selects.\n */\nexport interface WorkflowStageDefinition<\n\tInputs,\n\tName extends WorkflowStageName<Inputs>,\n\tOutput,\n\tResult,\n> {\n\t/**\n\t * Creates the stage's agent, with a context of its own, the first time the stage runs in a\n\t * run. When the run comes back to the stage, the same agent goes on with its conversation;\n\t * the next run creates another. It may be async, as `createLLMOrchestrator` is.\n\t */\n\tagent(): Agent | PromiseLike<Agent>;\n\tprompt(input: Inputs[Name]): string | PromiseLike<string>;\n\t/** Receives the agent's answer parsed as JSON. */\n\treadonly validator: Validator<Output>;\n\t/** Receives the validated answer and the stage's own input, to carry forward what the next stage needs. */\n\ttransition(\n\t\toutput: Output,\n\t\tinput: Inputs[Name],\n\t): WorkflowTransition<Inputs, Result> | PromiseLike<WorkflowTransition<Inputs, Result>>;\n\t/** How many answers the stage may give before an invalid one fails the run. */\n\treadonly maxAttempts?: number;\n}\n\n/** A stage bound to its name, as `defineWorkflowStages` returns it. */\nexport interface WorkflowStage<\n\tInputs,\n\tResult,\n\tName extends WorkflowStageName<Inputs> = WorkflowStageName<Inputs>,\n> extends WorkflowStageDefinition<Inputs, Name, unknown, Result> {\n\treadonly name: Name;\n}\n\n/**\n * Binds stage definitions to one workflow's map of stage inputs and its result. The function it\n * returns infers each stage's output from its validator.\n *\n * @example\n * const stage = defineWorkflowStages<{ triage: string; draft: Brief }, string>();\n * const triage = stage('triage', { agent, prompt, validator, transition });\n */\nexport function defineWorkflowStages<Inputs, Result>(): <\n\tName extends WorkflowStageName<Inputs>,\n\tOutput,\n>(\n\tname: Name,\n\tdefinition: WorkflowStageDefinition<Inputs, Name, Output, Result>,\n) => WorkflowStage<Inputs, Result, Name> {\n\treturn (name, definition) => ({ ...definition, name });\n}\n\nexport interface WorkflowStageReport {\n\tstage: string;\n\t/** What the stage received. */\n\tinput: unknown;\n\t/** The stage's validated answer. */\n\toutput: unknown;\n\t/** How many answers the stage's agent gave, the last one valid. */\n\tattempts: number;\n\t/** Seconds. */\n\tduration: number;\n\t/** Absent when a provider did not report an answer's usage. */\n\ttokens?: Tokens;\n\ttransition: 'go' | 'stop' | 'complete';\n\t/** The stage that ran next, absent after `stop` or `complete`. */\n\tnextStage?: string;\n}\n\nexport interface WorkflowResult<Result> {\n\t/** `stopped` is a deliberate early end, not a failure: a failure rejects the run. */\n\tstatus: 'completed' | 'stopped';\n\tresult: Result;\n\t/** Every stage's usage. Absent when any answer's usage was not reported. */\n\ttokens?: Tokens;\n\t/** Seconds. */\n\tduration: number;\n\trunId?: string;\n\t/** One report per stage run, in order: what each stage received, answered and chose. */\n\tstages: readonly WorkflowStageReport[];\n}\n\nexport interface Workflow<Input, Result> {\n\trun(input: Input, options: RunOptions): Promise<WorkflowResult<Result>>;\n}\n\nexport interface CreateWorkflowOptions<\n\tInputs,\n\tResult,\n\tInitial extends WorkflowStageName<Inputs> = WorkflowStageName<Inputs>,\n> {\n\tinitialStage: Initial;\n\t/** Every stage, under its own name. */\n\tstages: {\n\t\treadonly [Name in WorkflowStageName<Inputs>]: WorkflowStage<Inputs, Result, Name>;\n\t} & Readonly<Record<string, WorkflowStage<Inputs, Result>>>;\n\t/** The default for a stage without its own. Defaults to 3. */\n\tmaxAttempts?: number;\n\t/** How many times a run may `go` to a stage, so a loop of stages ends. Defaults to 100. */\n\tmaxTransitions?: number;\n\tlogger?: ILogger;\n}\n"]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Agent, RunOptions } from '#src/agent/domain/agent';
|
|
2
|
+
import type { ILogger } from '#src/shared/domain/logger';
|
|
3
|
+
import type { Validator } from '#src/shared/domain/validator';
|
|
4
|
+
import type { Workflow, WorkflowResult } from './workflow.ts';
|
|
5
|
+
/** A stage with its types erased: the factory checked them, and the runner only passes values on. */
|
|
6
|
+
export interface RunnableStage {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
agent(): Agent | PromiseLike<Agent>;
|
|
9
|
+
prompt(input: unknown): string | PromiseLike<string>;
|
|
10
|
+
readonly validator: Validator<unknown>;
|
|
11
|
+
transition(output: unknown, input: unknown): unknown;
|
|
12
|
+
readonly maxAttempts?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Runs the stages the factory already checked: one stage at a time, each answer validated before
|
|
16
|
+
* its transition is taken, and only the transition's `input` reaching the next stage. Each stage
|
|
17
|
+
* creates its agent once per run, so a run never sees another run's conversations.
|
|
18
|
+
*/
|
|
19
|
+
export declare class WorkflowRunner<Input, Result> implements Workflow<Input, Result> {
|
|
20
|
+
private readonly stages;
|
|
21
|
+
private readonly initialStage;
|
|
22
|
+
private readonly maxAttempts;
|
|
23
|
+
private readonly maxTransitions;
|
|
24
|
+
private readonly logger;
|
|
25
|
+
constructor(options: {
|
|
26
|
+
initialStage: string;
|
|
27
|
+
stages: ReadonlyMap<string, RunnableStage>;
|
|
28
|
+
maxAttempts: number;
|
|
29
|
+
maxTransitions: number;
|
|
30
|
+
logger: ILogger;
|
|
31
|
+
});
|
|
32
|
+
/**
|
|
33
|
+
* Every stage of one run shares one run context, so what their agents write is one run of the
|
|
34
|
+
* workspace's history. The workflow ends that context with the run, unless an outer agent owns it.
|
|
35
|
+
*/
|
|
36
|
+
run(input: Input, options: RunOptions): Promise<WorkflowResult<Result>>;
|
|
37
|
+
/** `agents` holds the agent each stage created in this run, so a stage the run comes back to goes on with it. */
|
|
38
|
+
private runStages;
|
|
39
|
+
/** The stage's agent in this run: created the first time the stage runs, then reused. */
|
|
40
|
+
private agentOf;
|
|
41
|
+
/**
|
|
42
|
+
* Asks the stage's agent until an answer is valid JSON its validator accepts, or attempts run
|
|
43
|
+
* out. Each answer's usage goes to the run's and the stage's totals; a failure carries the
|
|
44
|
+
* run's when `run` rethrows it.
|
|
45
|
+
*/
|
|
46
|
+
private answer;
|
|
47
|
+
private runAgent;
|
|
48
|
+
private transition;
|
|
49
|
+
private stageNamed;
|
|
50
|
+
/** The run's records end here, but its result stands: what it changed is already on disk. */
|
|
51
|
+
private finishContext;
|
|
52
|
+
private emit;
|
|
53
|
+
private warn;
|
|
54
|
+
}
|