@mikode13/harness 3.0.0 → 3.1.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 +72 -6
- package/dist/factory/infrastructure/agentLLMFactory.d.ts +8 -6
- package/dist/factory/infrastructure/agentLLMFactory.js +8 -6
- package/dist/factory/infrastructure/agentLLMFactory.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/tools/infrastructure/createWorkspace.d.ts +2 -1
- package/dist/tools/infrastructure/createWorkspace.js +2 -1
- package/dist/tools/infrastructure/createWorkspace.js.map +1 -1
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -32,11 +32,16 @@ multi-agent workflow, chosen entirely by what gets wired up in `cli/cli.ts`; the
|
|
|
32
32
|
chat loop itself never knows the difference.
|
|
33
33
|
|
|
34
34
|
- **A provider-agnostic `Agent` contract** (`run(prompt, signal, callback)`) with
|
|
35
|
-
two
|
|
36
|
-
|
|
37
|
-
- **
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
two paths to each provider — swapping one agent for another, anywhere in the
|
|
36
|
+
composition, changes nothing else.
|
|
37
|
+
- **The Agent SDK path, the default**: `CodexAgent` and `ClaudeAgent` drive Codex
|
|
38
|
+
and Claude Code. Codex reuses one `Thread` across turns and Claude resumes via a
|
|
39
|
+
captured `session_id`, so the provider keeps the context.
|
|
40
|
+
- **The model API path**: `LLMAgent` calls the OpenAI Responses API or the
|
|
41
|
+
Anthropic Messages API and keeps the conversation itself, in a form that can move
|
|
42
|
+
between providers. It runs its own tool loop over the tools it is given; the
|
|
43
|
+
harness ships three that only read the repository (list, search, read), bounded
|
|
44
|
+
by `.gitignore`.
|
|
40
45
|
- **`OrchestratorAgent`**: coordinates a planner, an executor, and a reviewer
|
|
41
46
|
(each an injected `Agent`) in a plan → execute → review loop. The reviewer's
|
|
42
47
|
decision is a Zod-validated structured `{decision, feedback}`, not free text —
|
|
@@ -96,9 +101,12 @@ comment on `Agent` in `src/agent/domain/agent.ts`.
|
|
|
96
101
|
## Install
|
|
97
102
|
|
|
98
103
|
```sh
|
|
99
|
-
pnpm add @mikode13/harness
|
|
104
|
+
pnpm add @mikode13/harness zod
|
|
100
105
|
```
|
|
101
106
|
|
|
107
|
+
`zod` 4 is a peer dependency: `defineTool` takes your Zod schemas, so the harness uses
|
|
108
|
+
your copy rather than bundling its own.
|
|
109
|
+
|
|
102
110
|
The package is ESM only and runs on Node.js 22 (22.13 or later) or 24. Its type declarations resolve
|
|
103
111
|
internal modules through the `imports` field of its `package.json`, so a TypeScript consumer
|
|
104
112
|
needs `moduleResolution` set to `node16`, `nodenext` or `bundler`; the legacy `node10`
|
|
@@ -147,6 +155,59 @@ reasoning effort the chosen provider does not support throws
|
|
|
147
155
|
`UnrecoverableError` if the Codex CLI binary cannot be found, which happens when
|
|
148
156
|
optional dependencies were skipped at install time.
|
|
149
157
|
|
|
158
|
+
### Authentication
|
|
159
|
+
|
|
160
|
+
The two paths authenticate differently:
|
|
161
|
+
|
|
162
|
+
- `createAgent` and `createOrchestrator` use the Agent SDKs, which use the login of
|
|
163
|
+
the Codex CLI and of Claude Code, so a run counts against that subscription.
|
|
164
|
+
- `createLLMAgent` and the planner and reviewer of `createLLMOrchestrator` call the
|
|
165
|
+
model APIs, which bill per token and need the key of each provider they use in
|
|
166
|
+
the environment: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, or both for the
|
|
167
|
+
orchestrator's default roles. A missing OpenAI key fails when the agent is built; a missing
|
|
168
|
+
Anthropic key fails its first run.
|
|
169
|
+
|
|
170
|
+
The Claude Agent SDK
|
|
171
|
+
[authenticates with `ANTHROPIC_API_KEY`](https://code.claude.com/docs/en/agent-sdk) whenever
|
|
172
|
+
it is set, so with that key in the environment a Claude agent from `createAgent`, or the executor of
|
|
173
|
+
`createLLMOrchestrator({ provider: 'anthropic' })`, bills the key instead of the
|
|
174
|
+
subscription.
|
|
175
|
+
|
|
176
|
+
### Agents that own their conversation
|
|
177
|
+
|
|
178
|
+
`createLLMAgent` takes a system prompt and the tools the model may call, and
|
|
179
|
+
returns the same `Agent`. `defineTool` builds a tool from a Zod 4 schema; a field the
|
|
180
|
+
model may leave out is `.nullable()`, not optional, because both APIs call tools in
|
|
181
|
+
strict mode:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import {
|
|
185
|
+
createLLMAgent,
|
|
186
|
+
createWorkspace,
|
|
187
|
+
createWorkspaceTools,
|
|
188
|
+
defineTool,
|
|
189
|
+
} from '@mikode13/harness';
|
|
190
|
+
import { z } from 'zod';
|
|
191
|
+
|
|
192
|
+
const now = defineTool({
|
|
193
|
+
name: 'now',
|
|
194
|
+
description: 'The current time, in ISO 8601.',
|
|
195
|
+
input: z.object({}),
|
|
196
|
+
execute: async () => new Date().toISOString(),
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const workspace = await createWorkspace({ root: process.cwd() });
|
|
200
|
+
const agent = createLLMAgent('openai', {
|
|
201
|
+
systemPrompt: 'You answer questions about this repository.',
|
|
202
|
+
tools: [...createWorkspaceTools(workspace), now],
|
|
203
|
+
});
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The agent runs every tool it is given, without asking: there is no approval step
|
|
207
|
+
yet, so only give it tools you would let run unattended. `createLLMOrchestrator()`
|
|
208
|
+
takes the same options as `createOrchestrator()` and returns a promise; its executor
|
|
209
|
+
still runs on an Agent SDK, because the harness's own tools cannot change files.
|
|
210
|
+
|
|
150
211
|
## Tests
|
|
151
212
|
|
|
152
213
|
`pnpm test` runs the unit suite against deterministic fakes; it never contacts a
|
|
@@ -164,6 +225,11 @@ exists solely to exercise the library manually while working in this repository:
|
|
|
164
225
|
pnpm run dev
|
|
165
226
|
```
|
|
166
227
|
|
|
228
|
+
The agents work on the repository root, which is the directory the script runs in.
|
|
229
|
+
`pnpm run dev --llm` plans and reviews on the model APIs instead, through
|
|
230
|
+
`createLLMOrchestrator`, and needs the API keys in the environment (see
|
|
231
|
+
[Authentication](#authentication)).
|
|
232
|
+
|
|
167
233
|
Type your prompt at `>`. Press Ctrl+C while idle at the prompt to exit; pressing
|
|
168
234
|
it while an agent is running cancels only that turn and returns to the prompt.
|
|
169
235
|
|
|
@@ -19,11 +19,12 @@ export interface CreateLLMAgentOptions {
|
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
21
|
* Builds an agent whose conversation MiKode owns, on a provider's model API, already wrapped in
|
|
22
|
-
* the harness's retry policy.
|
|
23
|
-
*
|
|
22
|
+
* the harness's retry policy. It needs the provider's API key and bills per token, where
|
|
23
|
+
* `createAgent` uses the Agent SDK's login. It has no `autoApprove`: it runs whatever tools it
|
|
24
|
+
* is given, so a caller who gives it tools that change things owns that decision.
|
|
24
25
|
*
|
|
25
|
-
* @throws {InvalidAgentConfigError} for an unknown provider,
|
|
26
|
-
* client does not support.
|
|
26
|
+
* @throws {InvalidAgentConfigError} for an unknown provider, a model or reasoning effort its
|
|
27
|
+
* client does not support, or two tools that share a name.
|
|
27
28
|
* @throws {UnrecoverableError} when the client cannot be set up, for example because
|
|
28
29
|
* `OPENAI_API_KEY` is missing. The Anthropic SDK resolves credentials lazily, so a missing
|
|
29
30
|
* `ANTHROPIC_API_KEY` fails the first run instead, with an `UnrecoverableError`.
|
|
@@ -33,8 +34,9 @@ export declare function createLLMAgent(provider: AgentProvider, { model, reasoni
|
|
|
33
34
|
* Builds the planner → executor → reviewer workflow with the planner and reviewer on the model
|
|
34
35
|
* APIs, from `createLLMAgent`, each holding its role's instructions, or `systemPrompts`, as its
|
|
35
36
|
* system prompt, with the read-only repository tools for the current working directory. The
|
|
36
|
-
* executor stays on its Agent SDK, from `createAgent`, until an agent of ours can change files;
|
|
37
|
-
* only it. Asynchronous because finding the program that reads the
|
|
37
|
+
* executor stays on its Agent SDK, from `createAgent`, until an agent of ours can change files;
|
|
38
|
+
* `autoApprove` reaches only it. Asynchronous because finding the program that reads the
|
|
39
|
+
* repository is.
|
|
38
40
|
*
|
|
39
41
|
* @throws {InvalidAgentConfigError} for an unknown provider.
|
|
40
42
|
* @throws {UnrecoverableError} when a role cannot be set up, for example because
|
|
@@ -17,11 +17,12 @@ const defaultLLMAgentModels = {
|
|
|
17
17
|
};
|
|
18
18
|
/**
|
|
19
19
|
* Builds an agent whose conversation MiKode owns, on a provider's model API, already wrapped in
|
|
20
|
-
* the harness's retry policy.
|
|
21
|
-
*
|
|
20
|
+
* the harness's retry policy. It needs the provider's API key and bills per token, where
|
|
21
|
+
* `createAgent` uses the Agent SDK's login. It has no `autoApprove`: it runs whatever tools it
|
|
22
|
+
* is given, so a caller who gives it tools that change things owns that decision.
|
|
22
23
|
*
|
|
23
|
-
* @throws {InvalidAgentConfigError} for an unknown provider,
|
|
24
|
-
* client does not support.
|
|
24
|
+
* @throws {InvalidAgentConfigError} for an unknown provider, a model or reasoning effort its
|
|
25
|
+
* client does not support, or two tools that share a name.
|
|
25
26
|
* @throws {UnrecoverableError} when the client cannot be set up, for example because
|
|
26
27
|
* `OPENAI_API_KEY` is missing. The Anthropic SDK resolves credentials lazily, so a missing
|
|
27
28
|
* `ANTHROPIC_API_KEY` fails the first run instead, with an `UnrecoverableError`.
|
|
@@ -65,8 +66,9 @@ const repositoryGuidance = 'Paths are relative to the repository root. Start wit
|
|
|
65
66
|
* Builds the planner → executor → reviewer workflow with the planner and reviewer on the model
|
|
66
67
|
* APIs, from `createLLMAgent`, each holding its role's instructions, or `systemPrompts`, as its
|
|
67
68
|
* system prompt, with the read-only repository tools for the current working directory. The
|
|
68
|
-
* executor stays on its Agent SDK, from `createAgent`, until an agent of ours can change files;
|
|
69
|
-
* only it. Asynchronous because finding the program that reads the
|
|
69
|
+
* executor stays on its Agent SDK, from `createAgent`, until an agent of ours can change files;
|
|
70
|
+
* `autoApprove` reaches only it. Asynchronous because finding the program that reads the
|
|
71
|
+
* repository is.
|
|
70
72
|
*
|
|
71
73
|
* @throws {InvalidAgentConfigError} for an unknown provider.
|
|
72
74
|
* @throws {UnrecoverableError} when a role cannot be set up, for example because
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agentLLMFactory.js","sourceRoot":"","sources":["../../../src/factory/infrastructure/agentLLMFactory.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAC;AAE9D,OAAO,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,iDAAiD,CAAC;AAClF,OAAO,EACN,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,EACnB,oBAAoB,GACpB,MAAM,mDAAmD,CAAC;AAC3D,OAAO,EAAE,yBAAyB,EAAE,MAAM,mEAAmE,CAAC;AAC9G,OAAO,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AAEtE,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAE3D,OAAO,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAa,MAAM,mBAAmB,CAAC;AACjF,OAAO,EACN,cAAc,GAKd,MAAM,YAAY,CAAC;AAkBpB,MAAM,qBAAqB,GAAG;IAC7B,SAAS,EAAE,QAAQ;IACnB,MAAM,EAAE,cAAc;CAC+B,CAAC;AAEvD
|
|
1
|
+
{"version":3,"file":"agentLLMFactory.js","sourceRoot":"","sources":["../../../src/factory/infrastructure/agentLLMFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Agent } from '#src/agent/domain/agent';\nimport { InvalidAgentConfigError } from '#src/agent/domain/errors';\nimport { LLMAgent } from '#src/engines/domain/model/llmAgent';\nimport type { LLMClient } from '#src/llm/domain/llm';\nimport { ClaudeLLMClient } from '#src/llm/infrastructure/claudeLLMClient';\nimport { OpenAILLMClient } from '#src/llm/infrastructure/openAILLMClient';\nimport { InstructedAgent } from '#src/orchestration/domain/model/instructedAgent';\nimport {\n\texecutorInstructions,\n\tOrchestratorAgent,\n\tplannerInstructions,\n\treviewerInstructions,\n} from '#src/orchestration/domain/model/orchestratorAgent';\nimport { ReviewerDecisionValidator } from '#src/orchestration/infrastructure/model/reviewerDecisionValidator';\nimport { RetryingAgent } from '#src/retry/domain/model/retryingAgent';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport { Logger } from '#src/shared/infrastructure/logger';\nimport type { Tool } from '#src/tools/domain/tool';\nimport { createWorkspace } from '#src/tools/infrastructure/createWorkspace';\nimport { createWorkspaceTools } from '#src/tools/infrastructure/workspaceTools';\nimport { createAgent, orchestratorRolesFor, type Role } from './agentFactory.ts';\nimport {\n\tagentProviders,\n\ttype AgentModel,\n\ttype AgentProvider,\n\ttype CreateOrchestratorOptions,\n\ttype ReasoningEffort,\n} from './types.ts';\n\nexport interface CreateLLMAgentOptions {\n\t/**\n\t * Defaults to `'sonnet'` on Anthropic and `'gpt-5.6-luna'` on OpenAI. The provider's client\n\t * rejects a model it does not support.\n\t */\n\tmodel?: AgentModel;\n\t/** Defaults to `'high'`. The provider's client rejects an effort it does not support. */\n\treasoningEffort?: ReasoningEffort;\n\t/** Sent as the model's instructions on every call. */\n\tsystemPrompt: string;\n\t/** The tools the model may call. Defaults to none. */\n\ttools?: Tool[];\n\t/** Defaults to warnings on stderr. */\n\tlogger?: ILogger;\n}\n\nconst defaultLLMAgentModels = {\n\tanthropic: 'sonnet',\n\topenai: 'gpt-5.6-luna',\n} as const satisfies Record<AgentProvider, AgentModel>;\n\n/**\n * Builds an agent whose conversation MiKode owns, on a provider's model API, already wrapped in\n * the harness's retry policy. It needs the provider's API key and bills per token, where\n * `createAgent` uses the Agent SDK's login. It has no `autoApprove`: it runs whatever tools it\n * is given, so a caller who gives it tools that change things owns that decision.\n *\n * @throws {InvalidAgentConfigError} for an unknown provider, a model or reasoning effort its\n * client does not support, or two tools that share a name.\n * @throws {UnrecoverableError} when the client cannot be set up, for example because\n * `OPENAI_API_KEY` is missing. The Anthropic SDK resolves credentials lazily, so a missing\n * `ANTHROPIC_API_KEY` fails the first run instead, with an `UnrecoverableError`.\n */\nexport function createLLMAgent(\n\tprovider: AgentProvider,\n\t{ model, reasoningEffort, systemPrompt, tools, logger = new Logger() }: CreateLLMAgentOptions,\n): Agent {\n\tlet llmClient: LLMClient;\n\n\tswitch (provider) {\n\t\tcase 'anthropic':\n\t\t\tllmClient = new ClaudeLLMClient({\n\t\t\t\tmodel: model ?? defaultLLMAgentModels.anthropic,\n\t\t\t\treasoningEffort,\n\t\t\t\tsystemPrompt,\n\t\t\t\tlogger,\n\t\t\t});\n\t\t\tbreak;\n\t\tcase 'openai':\n\t\t\tllmClient = new OpenAILLMClient({\n\t\t\t\tmodel: model ?? defaultLLMAgentModels.openai,\n\t\t\t\treasoningEffort,\n\t\t\t\tsystemPrompt,\n\t\t\t\tlogger,\n\t\t\t});\n\t\t\tbreak;\n\t\tdefault: {\n\t\t\t// Reachable from untyped input.\n\t\t\tconst unknownProvider: never = provider;\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`\"${String(unknownProvider)}\" is not an agent provider; expected one of: ${agentProviders.join(', ')}`,\n\t\t\t);\n\t\t}\n\t}\n\n\t// LLMAgent records nothing from a failed call, so the original prompt is the whole story.\n\treturn new RetryingAgent({\n\t\tinner: new LLMAgent({ llmClient, tools }),\n\t\tlogger,\n\t\tnoteFailures: false,\n\t});\n}\n\n// Sent on every call after the role's instructions, so it stays short: it only says how to find\n// one's way around the repository without reading all of it.\nconst repositoryGuidance =\n\t'Paths are relative to the repository root. Start with AGENTS.md and the architecture document it links to learn where things live, then read only the files your task needs.';\n\n/**\n * Builds the planner → executor → reviewer workflow with the planner and reviewer on the model\n * APIs, from `createLLMAgent`, each holding its role's instructions, or `systemPrompts`, as its\n * system prompt, with the read-only repository tools for the current working directory. The\n * executor stays on its Agent SDK, from `createAgent`, until an agent of ours can change files;\n * `autoApprove` reaches only it. Asynchronous because finding the program that reads the\n * repository is.\n *\n * @throws {InvalidAgentConfigError} for an unknown provider.\n * @throws {UnrecoverableError} when a role cannot be set up, for example because\n * `OPENAI_API_KEY` or the Codex CLI binary is missing, or when no program can read the\n * repository.\n */\nexport async function createLLMOrchestrator({\n\tprovider,\n\tautoApprove,\n\tsystemPrompts = {},\n\tlogger = new Logger(),\n}: CreateOrchestratorOptions = {}): Promise<Agent> {\n\tconst roles = orchestratorRolesFor(provider);\n\tconst tools = createWorkspaceTools(await createWorkspace({ root: process.cwd(), logger }));\n\tconst llmAgentFor = ({ provider, model, reasoningEffort }: Role, systemPrompt: string) =>\n\t\tcreateLLMAgent(provider, { model, reasoningEffort, systemPrompt, tools, logger });\n\t// A caller's own prompt replaces the whole of ours, guidance included.\n\tconst withGuidance = (instructions: string) => `${instructions}\\n\\n${repositoryGuidance}`;\n\n\treturn new OrchestratorAgent({\n\t\tplannerAgent: llmAgentFor(\n\t\t\troles.planner,\n\t\t\tsystemPrompts.planner ?? withGuidance(plannerInstructions),\n\t\t),\n\t\texecutorAgent: new InstructedAgent({\n\t\t\tinner: createAgent(roles.executor.provider, {\n\t\t\t\tmodel: roles.executor.model,\n\t\t\t\treasoningEffort: roles.executor.reasoningEffort,\n\t\t\t\tautoApprove,\n\t\t\t\tlogger,\n\t\t\t}),\n\t\t\tinstructions: systemPrompts.executor ?? executorInstructions,\n\t\t}),\n\t\treviewerAgent: llmAgentFor(\n\t\t\troles.reviewer,\n\t\t\tsystemPrompts.reviewer ?? withGuidance(reviewerInstructions),\n\t\t),\n\t\treviewerDecisionValidator: new ReviewerDecisionValidator(),\n\t\tlogger,\n\t});\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
export type { ILogger } from './shared/domain/logger.ts';
|
|
2
2
|
export { createAgent, createOrchestrator, isAgentProvider, type CreateAgentOptions, } from './factory/infrastructure/agentFactory.ts';
|
|
3
|
+
export { createLLMAgent, createLLMOrchestrator, type CreateLLMAgentOptions, } from './factory/infrastructure/agentLLMFactory.ts';
|
|
3
4
|
export { agentProviders, type AgentModel, type AgentProvider, type CreateOrchestratorOptions, type ReasoningEffort, } from './factory/infrastructure/types.ts';
|
|
4
5
|
export { type Agent, type AgentResponse, type Callback, type ProgressEvent, } from './agent/domain/agent.ts';
|
|
5
6
|
export { InvalidAgentConfigError, RecoverableError, UnrecoverableError, } from './agent/domain/errors.ts';
|
|
6
7
|
export { isAbortError } from './shared/domain/isAbortError.ts';
|
|
7
8
|
export type { Tokens } from './shared/domain/tokens.ts';
|
|
9
|
+
export type { JSONSchema, ToolDefinition } from './llm/domain/tool.ts';
|
|
10
|
+
export type { Tool } from './tools/domain/tool.ts';
|
|
11
|
+
export type { TextMatch, Workspace } from './tools/domain/workspace.ts';
|
|
12
|
+
export { defineTool } from './tools/infrastructure/defineTool.ts';
|
|
13
|
+
export { createWorkspace } from './tools/infrastructure/createWorkspace.ts';
|
|
14
|
+
export { createWorkspaceTools } from './tools/infrastructure/workspaceTools.ts';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export { createAgent, createOrchestrator, isAgentProvider, } from "./factory/infrastructure/agentFactory.js";
|
|
2
|
+
export { createLLMAgent, createLLMOrchestrator, } from "./factory/infrastructure/agentLLMFactory.js";
|
|
2
3
|
export { agentProviders, } from "./factory/infrastructure/types.js";
|
|
3
4
|
export {} from "./agent/domain/agent.js";
|
|
4
5
|
export { InvalidAgentConfigError, RecoverableError, UnrecoverableError, } from "./agent/domain/errors.js";
|
|
5
6
|
export { isAbortError } from "./shared/domain/isAbortError.js";
|
|
7
|
+
export { defineTool } from "./tools/infrastructure/defineTool.js";
|
|
8
|
+
export { createWorkspace } from "./tools/infrastructure/createWorkspace.js";
|
|
9
|
+
export { createWorkspaceTools } from "./tools/infrastructure/workspaceTools.js";
|
|
6
10
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACN,WAAW,EACX,kBAAkB,EAClB,eAAe,GAEf,MAAM,0CAA0C,CAAC;AAClD,OAAO,EACN,cAAc,GAKd,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAKN,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACN,uBAAuB,EACvB,gBAAgB,EAChB,kBAAkB,GAClB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC","sourcesContent":["export type { ILogger } from './shared/domain/logger.ts';\nexport {\n\tcreateAgent,\n\tcreateOrchestrator,\n\tisAgentProvider,\n\ttype CreateAgentOptions,\n} from './factory/infrastructure/agentFactory.ts';\nexport {\n\tagentProviders,\n\ttype AgentModel,\n\ttype AgentProvider,\n\ttype CreateOrchestratorOptions,\n\ttype ReasoningEffort,\n} from './factory/infrastructure/types.ts';\nexport {\n\ttype Agent,\n\ttype AgentResponse,\n\ttype Callback,\n\ttype ProgressEvent,\n} from './agent/domain/agent.ts';\nexport {\n\tInvalidAgentConfigError,\n\tRecoverableError,\n\tUnrecoverableError,\n} from './agent/domain/errors.ts';\nexport { isAbortError } from './shared/domain/isAbortError.ts';\nexport type { Tokens } from './shared/domain/tokens.ts';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACN,WAAW,EACX,kBAAkB,EAClB,eAAe,GAEf,MAAM,0CAA0C,CAAC;AAClD,OAAO,EACN,cAAc,EACd,qBAAqB,GAErB,MAAM,6CAA6C,CAAC;AACrD,OAAO,EACN,cAAc,GAKd,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAKN,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACN,uBAAuB,EACvB,gBAAgB,EAChB,kBAAkB,GAClB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAK/D,OAAO,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAC","sourcesContent":["export type { ILogger } from './shared/domain/logger.ts';\nexport {\n\tcreateAgent,\n\tcreateOrchestrator,\n\tisAgentProvider,\n\ttype CreateAgentOptions,\n} from './factory/infrastructure/agentFactory.ts';\nexport {\n\tcreateLLMAgent,\n\tcreateLLMOrchestrator,\n\ttype CreateLLMAgentOptions,\n} from './factory/infrastructure/agentLLMFactory.ts';\nexport {\n\tagentProviders,\n\ttype AgentModel,\n\ttype AgentProvider,\n\ttype CreateOrchestratorOptions,\n\ttype ReasoningEffort,\n} from './factory/infrastructure/types.ts';\nexport {\n\ttype Agent,\n\ttype AgentResponse,\n\ttype Callback,\n\ttype ProgressEvent,\n} from './agent/domain/agent.ts';\nexport {\n\tInvalidAgentConfigError,\n\tRecoverableError,\n\tUnrecoverableError,\n} from './agent/domain/errors.ts';\nexport { isAbortError } from './shared/domain/isAbortError.ts';\nexport type { Tokens } from './shared/domain/tokens.ts';\nexport type { JSONSchema, ToolDefinition } from './llm/domain/tool.ts';\nexport type { Tool } from './tools/domain/tool.ts';\nexport type { TextMatch, Workspace } from './tools/domain/workspace.ts';\nexport { defineTool } from './tools/infrastructure/defineTool.ts';\nexport { createWorkspace } from './tools/infrastructure/createWorkspace.ts';\nexport { createWorkspaceTools } from './tools/infrastructure/workspaceTools.ts';\n"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { UnrecoverableError } from '#src/agent/domain/errors';
|
|
2
2
|
import { classifyHostFailure, treatErrors } from '#src/agent/domain/providerFailure';
|
|
3
|
+
import { Logger } from '#src/shared/infrastructure/logger';
|
|
3
4
|
import { GitWorkspace } from "./gitWorkspace.js";
|
|
4
5
|
import { RipgrepWorkspace } from "./ripgrepWorkspace.js";
|
|
5
6
|
import { runProcess } from "./runProcess.js";
|
|
@@ -36,7 +37,7 @@ async function isGitWorkTree(root) {
|
|
|
36
37
|
*
|
|
37
38
|
* @throws {UnrecoverableError} when neither ripgrep nor a git repository is available.
|
|
38
39
|
*/
|
|
39
|
-
export async function createWorkspace({ root, logger, }) {
|
|
40
|
+
export async function createWorkspace({ root, logger = new Logger(), }) {
|
|
40
41
|
const ripgrepPath = await loadRipgrep();
|
|
41
42
|
if (ripgrepPath !== undefined) {
|
|
42
43
|
return new RipgrepWorkspace({ root, ripgrepPath });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createWorkspace.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/createWorkspace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"createWorkspace.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/createWorkspace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,mCAAmC,CAAC;AAErF,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,gGAAgG;AAChG,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAEhC,KAAK,UAAU,WAAW;IACzB,IAAI,CAAC;QACJ,yEAAyE;QACzE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;QACnD,OAAO,MAAM,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAC;IAClB,CAAC;AACF,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,IAAY;IACxC,IAAI,CAAC;QACJ,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,uBAAuB,CAAC,EAAE;YAC5F,GAAG,EAAE,IAAI;YACT,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,iBAAiB,CAAC;SAC9C,CAAC,CAAC;QACH,OAAO,QAAQ,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,MAAM,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACR,6CAA6C;QAC7C,OAAO,KAAK,CAAC;IACd,CAAC;AACF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,EACrC,IAAI,EACJ,MAAM,GAAG,IAAI,MAAM,EAAE,GAKrB;IACA,MAAM,WAAW,GAAG,MAAM,WAAW,EAAE,CAAC;IACxC,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QAC/B,OAAO,IAAI,gBAAgB,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;IACpD,CAAC;IAED,IAAI,MAAM,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,WAAW,CACV,GAAG,EAAE;YACJ,MAAM,CAAC,IAAI,CACV,+EAA+E,CAC/E,CAAC;QACH,CAAC,EACD,mBAAmB,EACnB,0DAA0D,CAC1D,CAAC;QACF,OAAO,IAAI,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;IACnC,CAAC;IAED,MAAM,IAAI,kBAAkB,CAAC,mCAAmC,EAAE;QACjE,KAAK,EAAE,kDAAkD,IAAI,2BAA2B;KACxF,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { UnrecoverableError } from '#src/agent/domain/errors';\nimport { classifyHostFailure, treatErrors } from '#src/agent/domain/providerFailure';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport { Logger } from '#src/shared/infrastructure/logger';\nimport type { Workspace } from '../domain/workspace.ts';\nimport { GitWorkspace } from './gitWorkspace.ts';\nimport { RipgrepWorkspace } from './ripgrepWorkspace.ts';\nimport { runProcess } from './runProcess.ts';\n\n// Long enough for a slow disk; short enough that a hung git does not hold up building an agent.\nconst gitCheckTimeoutMs = 5_000;\n\nasync function loadRipgrep(): Promise<string | undefined> {\n\ttry {\n\t\t// Dynamic: the package throws on import when the platform has no binary.\n\t\tconst { rgPath } = await import('@vscode/ripgrep');\n\t\treturn rgPath;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nasync function isGitWorkTree(root: string): Promise<boolean> {\n\ttry {\n\t\tconst { stdout, exitCode } = await runProcess('git', ['rev-parse', '--is-inside-work-tree'], {\n\t\t\tcwd: root,\n\t\t\tsignal: AbortSignal.timeout(gitCheckTimeoutMs),\n\t\t});\n\t\treturn exitCode === 0 && stdout.trim() === 'true';\n\t} catch {\n\t\t// No git at all, or one that never answered.\n\t\treturn false;\n\t}\n}\n\n/**\n * Picks how to read `root`: the ripgrep that ships with the package, or git when this platform\n * has no ripgrep binary, which is logged because it is a degraded path. Plain `grep` is never a\n * fallback: it knows nothing of `.gitignore`. See \"`.gitignore` is the boundary of what an agent\n * reads\" in decisions.md.\n *\n * @throws {UnrecoverableError} when neither ripgrep nor a git repository is available.\n */\nexport async function createWorkspace({\n\troot,\n\tlogger = new Logger(),\n}: {\n\troot: string;\n\t/** Defaults to warnings on stderr. */\n\tlogger?: ILogger;\n}): Promise<Workspace> {\n\tconst ripgrepPath = await loadRipgrep();\n\tif (ripgrepPath !== undefined) {\n\t\treturn new RipgrepWorkspace({ root, ripgrepPath });\n\t}\n\n\tif (await isGitWorkTree(root)) {\n\t\ttreatErrors(\n\t\t\t() => {\n\t\t\t\tlogger.warn(\n\t\t\t\t\t'ripgrep is not available on this platform; reading the repository through git',\n\t\t\t\t);\n\t\t\t},\n\t\t\tclassifyHostFailure,\n\t\t\t'Workspace logger failed while choosing an implementation',\n\t\t);\n\t\treturn new GitWorkspace({ root });\n\t}\n\n\tthrow new UnrecoverableError('No program can read the workspace', {\n\t\tcause: `ripgrep is not available on this platform, and ${root} is not a git repository.`,\n\t});\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikode13/harness",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Test/agent harness utility",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"format:check": "prettier . --check",
|
|
39
39
|
"lint": "eslint . --max-warnings 0",
|
|
40
40
|
"lint:fix": "eslint . --fix",
|
|
41
|
-
"dev": "
|
|
41
|
+
"dev": "node --conditions=mikode-harness-source cli/cli.ts",
|
|
42
42
|
"test": "vitest run --project unit --project cli",
|
|
43
43
|
"test:unit": "vitest run --project unit --project cli",
|
|
44
44
|
"test:unit:watch": "vitest --project unit --project cli",
|
|
@@ -57,14 +57,17 @@
|
|
|
57
57
|
"eslint": "^10.8.0",
|
|
58
58
|
"prettier": "^3.9.6",
|
|
59
59
|
"typescript": "^6.0.3",
|
|
60
|
-
"vitest": "^4.1.11"
|
|
60
|
+
"vitest": "^4.1.11",
|
|
61
|
+
"zod": "^4.4.3"
|
|
61
62
|
},
|
|
62
63
|
"dependencies": {
|
|
63
64
|
"@anthropic-ai/claude-agent-sdk": "^0.3.220",
|
|
64
65
|
"@anthropic-ai/sdk": "^0.115.0",
|
|
65
66
|
"@openai/codex-sdk": "^0.154.0",
|
|
66
67
|
"@vscode/ripgrep": "^1.18.0",
|
|
67
|
-
"openai": "^7.23.0"
|
|
68
|
+
"openai": "^7.23.0"
|
|
69
|
+
},
|
|
70
|
+
"peerDependencies": {
|
|
68
71
|
"zod": "^4.4.3"
|
|
69
72
|
}
|
|
70
73
|
}
|