@mikode13/harness 5.5.0 → 6.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +112 -0
  2. package/dist/agent/domain/agent.d.ts +26 -2
  3. package/dist/agent/domain/agent.js.map +1 -1
  4. package/dist/factory/infrastructure/workflowFactory.d.ts +9 -0
  5. package/dist/factory/infrastructure/workflowFactory.js +42 -0
  6. package/dist/factory/infrastructure/workflowFactory.js.map +1 -0
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.js +2 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/llm/infrastructure/claudeLLMClient.js +26 -9
  11. package/dist/llm/infrastructure/claudeLLMClient.js.map +1 -1
  12. package/dist/orchestration/domain/model/orchestratorAgent.d.ts +1 -1
  13. package/dist/orchestration/domain/model/orchestratorAgent.js.map +1 -1
  14. package/dist/orchestration/infrastructure/model/reviewerDecisionValidator.d.ts +1 -1
  15. package/dist/orchestration/infrastructure/model/reviewerDecisionValidator.js.map +1 -1
  16. package/dist/shared/domain/isAbortError.d.ts +1 -0
  17. package/dist/shared/domain/isAbortError.js +2 -1
  18. package/dist/shared/domain/isAbortError.js.map +1 -1
  19. package/dist/shared/domain/validator.d.ts +4 -0
  20. package/dist/shared/domain/validator.js.map +1 -0
  21. package/dist/workflow/domain/workflow.d.ts +100 -0
  22. package/dist/workflow/domain/workflow.js +12 -0
  23. package/dist/workflow/domain/workflow.js.map +1 -0
  24. package/dist/workflow/domain/workflowRunner.d.ts +54 -0
  25. package/dist/workflow/domain/workflowRunner.js +271 -0
  26. package/dist/workflow/domain/workflowRunner.js.map +1 -0
  27. package/package.json +1 -1
  28. package/dist/orchestration/domain/interface/validator.d.ts +0 -3
  29. package/dist/orchestration/domain/interface/validator.js.map +0 -1
  30. /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":"AAOA,OAAO,EAAE,SAAS,EAAe,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAIlG,OAAO,EACN,mBAAmB,EACnB,eAAe,EACf,WAAW,GACX,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACN,UAAU,EAEV,YAAY,EACZ,cAAc,GACd,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAE/D,4FAA4F;AAC5F,8FAA8F;AAC9F,2EAA2E;AAC3E,MAAM,oBAAoB,GACzB,wYAAwY,CAAC;AAE1Y,MAAM,CAAC,MAAM,mBAAmB,GAAG,uXAAuX,oBAAoB,EAAE,CAAC;AAEjb,MAAM,YAAY,GAAG,2VAA2V,oBAAoB,EAAE,CAAC;AAEvY,6FAA6F;AAC7F,2FAA2F;AAC3F,gCAAgC;AAChC,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,YAAY,6SAA6S,CAAC;AAEjW,2FAA2F;AAC3F,oFAAoF;AACpF,MAAM,CAAC,MAAM,mCAAmC,GAAG,GAAG,YAAY,iRAAiR,CAAC;AAEpV,MAAM,YAAY,GAAG,wxBAAwxB,oBAAoB,EAAE,CAAC;AAEp0B,6DAA6D;AAC7D,MAAM,CAAC,MAAM,oBAAoB,GAAG,6IAA6I,YAAY,EAAE,CAAC;AAEhM,MAAM,CAAC,MAAM,mCAAmC,GAAG,kOAAkO,YAAY,EAAE,CAAC;AAEpS,MAAM,gBAAgB,GAAG,CAAC,UAAkB,EAAE,qBAA8B,EAAE,EAAE;IAC/E,MAAM,QAAQ,GAAG,qBAAqB;QACrC,CAAC,CAAC,iDAAiD,qBAAqB,OAAO;QAC/E,CAAC,CAAC,EAAE,CAAC;IAEN,OAAO;;EAEN,UAAU;KACP,QAAQ,EAAE,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,iBAAiB,GAAG,CAAC,UAAkB,EAAE,aAAqB,EAAE,EAAE,CACvE;;EAEC,UAAU;;;;;EAKV,aAAa;IACX,CAAC;AAEL,MAAM,iBAAiB,GAAG,CACzB,UAAkB,EAClB,aAAqB,EACrB,cAAsB,EACtB,kBAA2B,EAC1B,EAAE;IACH,MAAM,WAAW,GAAG,kBAAkB;QACrC,CAAC,CAAC,iDAAiD,kBAAkB,6GAA6G;QAClL,CAAC,CAAC,EAAE,CAAC;IAEN,OAAO;;EAEN,UAAU;;;;;EAKV,aAAa;;;;;EAKb,cAAc;KACX,WAAW,EAAE,CAAC;AACnB,CAAC,CAAC;AAQF,4FAA4F;AAC5F,SAAS,WAAW,CAAC,MAAiB,EAAE,QAAuB;IAC9D,IAAI,CAAC,QAAQ,CAAC,MAAM;QAAE,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC;IAC/C,MAAM,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC3D,CAAC;AAED,uFAAuF;AACvF,SAAS,SAAS,CAAC,MAAiB;IACnC,OAAO,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC;AACtD,CAAC;AAED,SAAS,cAAc,CAAC,IAAY;IACnC,MAAM,KAAK,GAAG,mCAAmC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IACpE,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;AAC3B,CAAC;AAED,SAAS,qBAAqB,CAC7B,QAAuB,EACvB,yBAAsD;IAEtD,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACxB,MAAM,IAAI,gBAAgB,CAAC,6DAA6D,EAAE;YACzF,KAAK,EAAE,+BAA+B;SACtC,CAAC,CAAC;IACJ,CAAC;IAED,IAAI,cAAuB,CAAC;IAC5B,IAAI,CAAC;QACJ,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACR,MAAM,IAAI,gBAAgB,CAAC,uCAAuC,EAAE;YACnE,KAAK,EAAE,uCAAuC;SAC9C,CAAC,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,yBAAyB,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;IACpE,IAAI,CAAC,QAAQ,EAAE,CAAC;QACf,MAAM,IAAI,gBAAgB,CAAC,uCAAuC,EAAE;YACnE,KAAK,EAAE,sDAAsD;SAC7D,CAAC,CAAC;IACJ,CAAC;IAED,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED,MAAM,OAAO,iBAAiB;IACrB,YAAY,CAAQ;IACpB,aAAa,CAAQ;IACrB,aAAa,CAAQ;IACrB,yBAAyB,CAA8B;IACvD,WAAW,CAAS;IACpB,MAAM,CAAU;IAExB,YAAY,EACX,YAAY,EACZ,aAAa,EACb,aAAa,EACb,yBAAyB,EACzB,WAAW,GAAG,CAAC,EACf,MAAM,GAQN;QACA,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;YACvD,MAAM,IAAI,UAAU,CAAC,wCAAwC,CAAC,CAAC;QAChE,CAAC;QAED,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,yBAAyB,GAAG,yBAAyB,CAAC;QAC3D,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACtB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,GAAG,CAAC,MAAc,EAAE,OAAmB;QAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,wFAAwF;QACxF,MAAM,MAAM,GAAc,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;QACnE,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;QACxC,MAAM,OAAO,GAAG,SAAS,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9C,IAAI,GAAG,GAAW,QAAQ,CAAC;QAE3B,IAAI,CAAC;YACJ,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;YAC7F,GAAG,GAAG,WAAW,CAAC;QACnB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,YAAY,CAAC,KAAK,CAAC;gBAAE,GAAG,GAAG,WAAW,CAAC;YAC3C,gFAAgF;YAChF,MAAM,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;QAChE,CAAC;gBAAS,CAAC;YACV,IAAI,CAAC,SAAS;gBAAE,MAAM,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QACxD,CAAC;QAED,6EAA6E;QAC7E,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC;QACrC,OAAO;YACN,QAAQ,EAAE,sBAAsB;YAChC,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC;YACzB,sEAAsE;YACtE,QAAQ,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,IAAI;YACrC,GAAG,CAAC,CAAC,SAAS,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACzC,CAAC;IACH,CAAC;IAED,6FAA6F;IACrF,KAAK,CAAC,aAAa,CAAC,OAAmB,EAAE,GAAW;QAC3D,IAAI,CAAC;YACJ,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,IAAI,CAAC,wCAAwC,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC7E,CAAC;IACF,CAAC;IAEO,KAAK,CAAC,SAAS,CAAC,MAAc,EAAE,OAAmB,EAAE,MAAiB;QAC7E,IAAI,iBAAqC,CAAC;QAE1C,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,IAAI,CAAC,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC;YAC9D,MAAM,aAAa,GAAG,OAAO,KAAK,IAAI,CAAC,WAAW,CAAC;YAEnD,MAAM,eAAe,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CAClD,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,CAAC,EAC3C,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,SAAS,CAAC,CACvC,CAAC;YACF,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;YAErC,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,CAAC;gBAC/B,iBAAiB,GAAG,mCAAmC,CAAC;gBACxD,IAAI,aAAa,EAAE,CAAC;oBACnB,MAAM,IAAI,kBAAkB,CAAC,wBAAwB,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;gBACtF,CAAC;gBACD,IAAI,CAAC,IAAI,CACR,WAAW,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,4DAA4D,CAClH,CAAC;gBACF,SAAS;YACV,CAAC;YAED,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,GAAG,CACpD,iBAAiB,CAAC,MAAM,EAAE,eAAe,CAAC,QAAQ,CAAC,EACnD,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,UAAU,CAAC,CACxC,CAAC;YACF,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;YAEtC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC;gBAChC,iBAAiB,GAAG,oCAAoC,CAAC;gBACzD,IAAI,aAAa,EAAE,CAAC;oBACnB,MAAM,IAAI,kBAAkB,CAAC,wBAAwB,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;gBACtF,CAAC;gBAED,IAAI,CAAC,IAAI,CACR,WAAW,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,6DAA6D,CACnH,CAAC;gBACF,SAAS;YACV,CAAC;YAED,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,mBAAmB,CACtD,MAAM,EACN,eAAe,CAAC,QAAQ,EACxB,gBAAgB,CAAC,QAAQ,EACzB,OAAO,EACP,MAAM,CACN,CAAC;YAEF,IAAI,gBAAgB,CAAC,QAAQ,KAAK,UAAU;gBAAE,OAAO;YAErD,iBAAiB,GAAG,gBAAgB,CAAC,QAAQ,CAAC;YAC9C,IAAI,aAAa,EAAE,CAAC;gBACnB,MAAM,IAAI,kBAAkB,CAAC,wBAAwB,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;YACtF,CAAC;YAED,IAAI,CAAC,IAAI,CACR,WAAW,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,uEAAuE,EAC7H,iBAAiB,CACjB,CAAC;QACH,CAAC;QAED,MAAM,IAAI,kBAAkB,CAAC,wBAAwB,EAAE;YACtD,KAAK,EAAE,iBAAiB,IAAI,kBAAkB;SAC9C,CAAC,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,6EAA6E;IAC7E,6CAA6C;IACrC,KAAK,CAAC,mBAAmB,CAChC,UAAkB,EAClB,aAAqB,EACrB,cAAsB,EACtB,OAAmB,EACnB,MAAiB;QAEjB,IAAI,kBAAsC,CAAC;QAE3C,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,IAAI,CAAC,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC;YAC9D,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,GAAG,CACpD,iBAAiB,CAAC,UAAU,EAAE,aAAa,EAAE,cAAc,EAAE,kBAAkB,CAAC,EAChF,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,UAAU,CAAC,CACxC,CAAC;YAEF,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;YAEtC,IAAI,CAAC;gBACJ,OAAO,qBAAqB,CAAC,gBAAgB,EAAE,IAAI,CAAC,yBAAyB,CAAC,CAAC;YAChF,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,IAAI,CAAC,CAAC,KAAK,YAAY,gBAAgB,CAAC;oBAAE,MAAM,KAAK,CAAC;gBAEtD,kBAAkB,GAAG,KAAK,CAAC,KAAK,CAAC;gBACjC,IAAI,OAAO,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC;oBAClC,MAAM,IAAI,kBAAkB,CAAC,wBAAwB,EAAE,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;gBACvF,CAAC;gBAED,IAAI,CAAC,IAAI,CACR,qBAAqB,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,0CAA0C,EAC1G,kBAAkB,CAClB,CAAC;YACH,CAAC;QACF,CAAC;QAED,MAAM,IAAI,kBAAkB,CAAC,wBAAwB,EAAE;YACtD,KAAK,EAAE,kBAAkB,IAAI,kBAAkB;SAC/C,CAAC,CAAC;IACJ,CAAC;IAEO,IAAI,CAAC,GAAG,IAAe;QAC9B,WAAW,CACV,GAAG,EAAE;YACJ,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;QAC3B,CAAC,EACD,mBAAmB,EACnB,oDAAoD,CACpD,CAAC;IACH,CAAC;IAED;;;OAGG;IACK,cAAc,CAAC,OAAmB,EAAE,KAAoB;QAC/D,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACtC,OAAO;YACN,GAAG,OAAO;YACV,UAAU,EAAE,UAAU;gBACrB,CAAC,CAAC,CAAC,KAAoB,EAAE,EAAE;oBACzB,WAAW,CACV,GAAG,EAAE;wBACJ,UAAU,CAAC,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;oBACjC,CAAC,EACD,mBAAmB,EACnB,uCAAuC,CACvC,CAAC;gBACH,CAAC;gBACF,CAAC,CAAC,SAAS;SACZ,CAAC;IACH,CAAC;CACD","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 '../../domain/interface/validator.ts';
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 '../../domain/interface/validator.ts';\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
+ {"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 +1,2 @@
1
+ /** A deliberate stop: an `AbortError`, or the `TimeoutError` that `AbortSignal.timeout` aborts with. */
1
2
  export declare function isAbortError(e: unknown): e is Error;
@@ -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;AACtD,CAAC","sourcesContent":["export function isAbortError(e: unknown): e is Error {\n\treturn e instanceof Error && e.name === 'AbortError';\n}\n"]}
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,4 @@
1
+ /** Turns a model's parsed answer into the typed value a caller needs, or `undefined` when it does not fit. */
2
+ export interface Validator<T> {
3
+ validate(input: unknown): T | undefined;
4
+ }
@@ -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"]}
@@ -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
+ }
@@ -0,0 +1,271 @@
1
+ import { RunContext, runContextOf, withRunContext, } from '#src/agent/domain/runContext';
2
+ import { RecoverableError, UnrecoverableError, withSpentTokens } from '#src/shared/domain/errors';
3
+ import { isAbortError } from '#src/shared/domain/isAbortError';
4
+ import { classifyHostFailure, describeFailure, treatErrors, } from '#src/shared/domain/providerFailure';
5
+ import { addTokens } from '#src/shared/domain/tokens';
6
+ function addUsage(totals, response) {
7
+ if (response.tokens === undefined)
8
+ totals.unreported = true;
9
+ totals.tokens = addTokens(totals.tokens, response.tokens);
10
+ }
11
+ function knownTokens(totals) {
12
+ return totals.unreported ? undefined : totals.tokens;
13
+ }
14
+ function stripCodeFence(text) {
15
+ const match = /^```(?:json)?\s*([\s\S]*?)\s*```$/i.exec(text.trim());
16
+ return match?.[1] ?? text;
17
+ }
18
+ function parseJSON(text) {
19
+ try {
20
+ return { value: JSON.parse(stripCodeFence(text)) };
21
+ }
22
+ catch {
23
+ return undefined;
24
+ }
25
+ }
26
+ function retryNotice(reason) {
27
+ return `\n\nYour previous response could not be used: ${reason} Respond with JSON only, matching the schema exactly, with no surrounding text and no markdown code fences.`;
28
+ }
29
+ function has(record, field) {
30
+ return Object.prototype.hasOwnProperty.call(record, field);
31
+ }
32
+ /** The types only bind TypeScript callers; a JavaScript callback can return anything. */
33
+ function isTransition(value) {
34
+ if (typeof value !== 'object' || value === null || !('type' in value))
35
+ return false;
36
+ switch (value.type) {
37
+ case 'go':
38
+ return 'stage' in value && typeof value.stage === 'string' && has(value, 'input');
39
+ case 'stop':
40
+ case 'complete':
41
+ return has(value, 'result');
42
+ default:
43
+ return false;
44
+ }
45
+ }
46
+ /**
47
+ * Runs the stages the factory already checked: one stage at a time, each answer validated before
48
+ * its transition is taken, and only the transition's `input` reaching the next stage. Each stage
49
+ * creates its agent once per run, so a run never sees another run's conversations.
50
+ */
51
+ export class WorkflowRunner {
52
+ stages;
53
+ initialStage;
54
+ maxAttempts;
55
+ maxTransitions;
56
+ logger;
57
+ constructor(options) {
58
+ this.initialStage = options.initialStage;
59
+ this.stages = options.stages;
60
+ this.maxAttempts = options.maxAttempts;
61
+ this.maxTransitions = options.maxTransitions;
62
+ this.logger = options.logger;
63
+ }
64
+ /**
65
+ * Every stage of one run shares one run context, so what their agents write is one run of the
66
+ * workspace's history. The workflow ends that context with the run, unless an outer agent owns it.
67
+ */
68
+ async run(input, options) {
69
+ const start = Date.now();
70
+ // Per invocation, not per instance: one workflow can serve several runs at once.
71
+ const totals = { tokens: undefined, unreported: false };
72
+ const stages = [];
73
+ const inherited = runContextOf(options);
74
+ const context = inherited ?? new RunContext();
75
+ let end = 'failed';
76
+ let outcome;
77
+ try {
78
+ outcome = await this.runStages(input, inherited ? options : withRunContext(options, context), totals, stages, new Map());
79
+ end = 'completed';
80
+ }
81
+ catch (error) {
82
+ if (isAbortError(error) || options.signal.aborted)
83
+ end = 'cancelled';
84
+ // A failing stage carries its own tokens; the stages before it are in the totals.
85
+ throw withSpentTokens(error, totals.tokens, totals.unreported);
86
+ }
87
+ finally {
88
+ if (!inherited)
89
+ await this.finishContext(context, end);
90
+ }
91
+ // Read once the run ended: a run that changed nothing left the history then.
92
+ const { runId } = context.historyRun;
93
+ return {
94
+ ...outcome,
95
+ tokens: knownTokens(totals),
96
+ duration: (Date.now() - start) / 1000,
97
+ stages,
98
+ ...(!inherited && runId ? { runId } : {}),
99
+ };
100
+ }
101
+ /** `agents` holds the agent each stage created in this run, so a stage the run comes back to goes on with it. */
102
+ async runStages(input, options, totals, reports, agents) {
103
+ let stage = this.stageNamed(this.initialStage);
104
+ let stageInput = input;
105
+ for (let transitions = 0;; transitions++) {
106
+ const started = Date.now();
107
+ const stageTotals = { tokens: undefined, unreported: false };
108
+ const agent = await this.agentOf(stage, agents);
109
+ const { output, attempts } = await this.answer(stage, agent, stageInput, options, totals, stageTotals);
110
+ const transition = await this.transition(stage, output, stageInput);
111
+ options.signal.throwIfAborted();
112
+ const tokens = knownTokens(stageTotals);
113
+ const nextStage = transition.type === 'go' ? transition.stage : undefined;
114
+ reports.push({
115
+ stage: stage.name,
116
+ input: stageInput,
117
+ output,
118
+ attempts,
119
+ duration: (Date.now() - started) / 1000,
120
+ ...(tokens && { tokens }),
121
+ transition: transition.type,
122
+ ...(nextStage !== undefined && { nextStage }),
123
+ });
124
+ this.emit(options, {
125
+ type: 'workflowTransition',
126
+ stage: stage.name,
127
+ transition: transition.type,
128
+ ...(nextStage !== undefined && { nextStage }),
129
+ });
130
+ if (transition.type !== 'go') {
131
+ const status = transition.type === 'complete' ? 'completed' : 'stopped';
132
+ return { status, result: transition.result };
133
+ }
134
+ if (transitions + 1 > this.maxTransitions) {
135
+ throw new UnrecoverableError('Workflow transition limit exhausted', {
136
+ cause: `The run went to another stage more than ${String(this.maxTransitions)} times.`,
137
+ });
138
+ }
139
+ stage = this.stageNamed(transition.stage, stage.name);
140
+ stageInput = transition.input;
141
+ }
142
+ }
143
+ /** The stage's agent in this run: created the first time the stage runs, then reused. */
144
+ async agentOf(stage, agents) {
145
+ const existing = agents.get(stage.name);
146
+ if (existing)
147
+ return existing;
148
+ let agent;
149
+ try {
150
+ agent = await stage.agent();
151
+ }
152
+ catch (error) {
153
+ throw classifyHostFailure(error, `Workflow stage "${stage.name}" could not create its agent`);
154
+ }
155
+ agents.set(stage.name, agent);
156
+ return agent;
157
+ }
158
+ /**
159
+ * Asks the stage's agent until an answer is valid JSON its validator accepts, or attempts run
160
+ * out. Each answer's usage goes to the run's and the stage's totals; a failure carries the
161
+ * run's when `run` rethrows it.
162
+ */
163
+ async answer(stage, agent, input, options, totals, stageTotals) {
164
+ const maxAttempts = stage.maxAttempts ?? this.maxAttempts;
165
+ options.signal.throwIfAborted();
166
+ let prompt;
167
+ try {
168
+ prompt = await stage.prompt(input);
169
+ }
170
+ catch (error) {
171
+ throw classifyHostFailure(error, `Workflow stage "${stage.name}" could not build its prompt`);
172
+ }
173
+ let failureReason;
174
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
175
+ options.signal.throwIfAborted();
176
+ this.emit(options, { type: 'workflowStage', stage: stage.name, attempt });
177
+ const response = await this.runAgent(stage, agent, failureReason ? prompt + retryNotice(failureReason) : prompt, options);
178
+ addUsage(totals, response);
179
+ // An answer that arrives after a cancellation is not used.
180
+ options.signal.throwIfAborted();
181
+ addUsage(stageTotals, response);
182
+ const parsed = parseJSON(response.response);
183
+ if (parsed) {
184
+ let output;
185
+ try {
186
+ output = stage.validator.validate(parsed.value);
187
+ }
188
+ catch (error) {
189
+ throw classifyHostFailure(error, `Workflow stage "${stage.name}" could not validate its answer`);
190
+ }
191
+ if (output !== undefined)
192
+ return { output, attempts: attempt };
193
+ failureReason = 'it did not match the schema.';
194
+ }
195
+ else {
196
+ failureReason = 'it was not valid JSON.';
197
+ }
198
+ if (attempt < maxAttempts) {
199
+ this.warn(`Workflow stage "${stage.name}" answer ${String(attempt)}/${String(maxAttempts)} was unusable (${failureReason}); asking again`);
200
+ }
201
+ }
202
+ throw new UnrecoverableError('Workflow stage attempts exhausted', {
203
+ cause: `Stage "${stage.name}" gave no usable answer in ${String(maxAttempts)} attempts: the last one ${failureReason ?? ''}`,
204
+ });
205
+ }
206
+ async runAgent(stage, agent, prompt, options) {
207
+ try {
208
+ return await agent.run(prompt, options);
209
+ }
210
+ catch (error) {
211
+ // Whatever the agent threw once the run was cancelled, the run ends as cancelled.
212
+ options.signal.throwIfAborted();
213
+ if (isAbortError(error))
214
+ throw error;
215
+ if (error instanceof RecoverableError || error instanceof UnrecoverableError)
216
+ throw error;
217
+ throw new UnrecoverableError(`Workflow stage "${stage.name}" failed`, {
218
+ cause: describeFailure(error),
219
+ });
220
+ }
221
+ }
222
+ async transition(stage, output, input) {
223
+ let transition;
224
+ try {
225
+ transition = await stage.transition(output, input);
226
+ }
227
+ catch (error) {
228
+ throw classifyHostFailure(error, `Workflow stage "${stage.name}" could not choose its transition`);
229
+ }
230
+ if (!isTransition(transition)) {
231
+ throw new UnrecoverableError('Workflow transition is invalid', {
232
+ cause: `Stage "${stage.name}" returned something that is not a transition.`,
233
+ });
234
+ }
235
+ return transition;
236
+ }
237
+ stageNamed(name, from) {
238
+ const stage = this.stages.get(name);
239
+ if (!stage) {
240
+ throw new UnrecoverableError('Workflow stage does not exist', {
241
+ cause: from
242
+ ? `Stage "${from}" selected unknown stage "${name}".`
243
+ : `The workflow has no stage "${name}".`,
244
+ });
245
+ }
246
+ return stage;
247
+ }
248
+ /** The run's records end here, but its result stands: what it changed is already on disk. */
249
+ async finishContext(context, end) {
250
+ try {
251
+ await context.finish(end);
252
+ }
253
+ catch (error) {
254
+ this.warn(`The run could not close its records: ${describeFailure(error)}`);
255
+ }
256
+ }
257
+ emit(options, event) {
258
+ const { onProgress } = options;
259
+ if (!onProgress)
260
+ return;
261
+ treatErrors(() => {
262
+ onProgress(event);
263
+ }, classifyHostFailure, 'Workflow progress callback failed');
264
+ }
265
+ warn(message) {
266
+ treatErrors(() => {
267
+ this.logger.warn(message);
268
+ }, classifyHostFailure, 'Workflow logger failed while reporting a retry');
269
+ }
270
+ }
271
+ //# sourceMappingURL=workflowRunner.js.map