@memberjunction/ai-agent-harness 0.0.0 → 6.1.0-edge.2
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/LICENSE +7 -0
- package/README.md +192 -27
- package/dist/HarnessAgentBase.d.ts +261 -0
- package/dist/HarnessAgentBase.d.ts.map +1 -0
- package/dist/HarnessAgentBase.js +822 -0
- package/dist/HarnessAgentBase.js.map +1 -0
- package/dist/HarnessAgentType.d.ts +39 -0
- package/dist/HarnessAgentType.d.ts.map +1 -0
- package/dist/HarnessAgentType.js +50 -0
- package/dist/HarnessAgentType.js.map +1 -0
- package/dist/adapters/BaseCliHarnessAdapter.d.ts +93 -0
- package/dist/adapters/BaseCliHarnessAdapter.d.ts.map +1 -0
- package/dist/adapters/BaseCliHarnessAdapter.js +184 -0
- package/dist/adapters/BaseCliHarnessAdapter.js.map +1 -0
- package/dist/adapters/BaseHarnessAdapter.d.ts +114 -0
- package/dist/adapters/BaseHarnessAdapter.d.ts.map +1 -0
- package/dist/adapters/BaseHarnessAdapter.js +86 -0
- package/dist/adapters/BaseHarnessAdapter.js.map +1 -0
- package/dist/adapters/ClaudeCodeCliAdapter.d.ts +104 -0
- package/dist/adapters/ClaudeCodeCliAdapter.d.ts.map +1 -0
- package/dist/adapters/ClaudeCodeCliAdapter.js +268 -0
- package/dist/adapters/ClaudeCodeCliAdapter.js.map +1 -0
- package/dist/adapters/CodexAdapter.d.ts +27 -0
- package/dist/adapters/CodexAdapter.d.ts.map +1 -0
- package/dist/adapters/CodexAdapter.js +117 -0
- package/dist/adapters/CodexAdapter.js.map +1 -0
- package/dist/adapters/GeminiCliAdapter.d.ts +25 -0
- package/dist/adapters/GeminiCliAdapter.d.ts.map +1 -0
- package/dist/adapters/GeminiCliAdapter.js +98 -0
- package/dist/adapters/GeminiCliAdapter.js.map +1 -0
- package/dist/adapters/OpenCodeAdapter.d.ts +23 -0
- package/dist/adapters/OpenCodeAdapter.d.ts.map +1 -0
- package/dist/adapters/OpenCodeAdapter.js +104 -0
- package/dist/adapters/OpenCodeAdapter.js.map +1 -0
- package/dist/adapters/PiAdapter.d.ts +73 -0
- package/dist/adapters/PiAdapter.d.ts.map +1 -0
- package/dist/adapters/PiAdapter.js +237 -0
- package/dist/adapters/PiAdapter.js.map +1 -0
- package/dist/adapters/StdioJsonAdapter.d.ts +43 -0
- package/dist/adapters/StdioJsonAdapter.d.ts.map +1 -0
- package/dist/adapters/StdioJsonAdapter.js +109 -0
- package/dist/adapters/StdioJsonAdapter.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/sandbox/ChildProcessExecutor.d.ts +41 -0
- package/dist/sandbox/ChildProcessExecutor.d.ts.map +1 -0
- package/dist/sandbox/ChildProcessExecutor.js +86 -0
- package/dist/sandbox/ChildProcessExecutor.js.map +1 -0
- package/dist/sandbox/DockerSandboxProvider.d.ts +64 -0
- package/dist/sandbox/DockerSandboxProvider.d.ts.map +1 -0
- package/dist/sandbox/DockerSandboxProvider.js +176 -0
- package/dist/sandbox/DockerSandboxProvider.js.map +1 -0
- package/dist/sandbox/ISandboxProvider.d.ts +71 -0
- package/dist/sandbox/ISandboxProvider.d.ts.map +1 -0
- package/dist/sandbox/ISandboxProvider.js +2 -0
- package/dist/sandbox/ISandboxProvider.js.map +1 -0
- package/dist/sandbox/LocalDirectorySandboxProvider.d.ts +37 -0
- package/dist/sandbox/LocalDirectorySandboxProvider.d.ts.map +1 -0
- package/dist/sandbox/LocalDirectorySandboxProvider.js +75 -0
- package/dist/sandbox/LocalDirectorySandboxProvider.js.map +1 -0
- package/dist/sandbox/SandboxExecutor.d.ts +47 -0
- package/dist/sandbox/SandboxExecutor.d.ts.map +1 -0
- package/dist/sandbox/SandboxExecutor.js +2 -0
- package/dist/sandbox/SandboxExecutor.js.map +1 -0
- package/dist/types.d.ts +180 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +35 -8
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ISandboxProvider.js","sourceRoot":"","sources":["../../src/sandbox/ISandboxProvider.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ISandboxProvider, SandboxConfig, SandboxHandle, WorkspaceKey } from './ISandboxProvider.js';
|
|
2
|
+
/**
|
|
3
|
+
* Phase-1 sandbox: a scoped directory on the MJ server's own filesystem.
|
|
4
|
+
*
|
|
5
|
+
* ## What this does and does not protect against
|
|
6
|
+
*
|
|
7
|
+
* It gives each run a workspace of the right lifetime and keeps agents out of each other's files.
|
|
8
|
+
* It does **not** contain the harness process: a determined harness can read outside its workspace,
|
|
9
|
+
* and `NetworkPolicy` is advisory here because nothing intercepts the process's sockets. Real
|
|
10
|
+
* enforcement needs the container provider, and `mcp-only` is the recommended production posture
|
|
11
|
+
* precisely because it is the one that can actually be enforced.
|
|
12
|
+
*
|
|
13
|
+
* Saying so plainly matters more than the code: an operator who believes `networkPolicy: 'none'`
|
|
14
|
+
* is enforced by this provider has a false sense of containment, which is worse than knowing the
|
|
15
|
+
* boundary is soft.
|
|
16
|
+
*/
|
|
17
|
+
export declare class LocalDirectorySandboxProvider implements ISandboxProvider {
|
|
18
|
+
private readonly rootPath;
|
|
19
|
+
/**
|
|
20
|
+
* @param rootPath Directory under which all workspaces are created. Defaults to a folder in the
|
|
21
|
+
* OS temp dir, which is fine for `run` scope but should be pointed somewhere
|
|
22
|
+
* durable when using `agent` or `agent-user` scopes, since temp dirs get swept.
|
|
23
|
+
*/
|
|
24
|
+
constructor(rootPath?: string);
|
|
25
|
+
/** @inheritdoc */
|
|
26
|
+
Provision(key: WorkspaceKey, _config: SandboxConfig): Promise<SandboxHandle>;
|
|
27
|
+
/** @inheritdoc */
|
|
28
|
+
Finalize(handle: SandboxHandle, _outcome: 'success' | 'failure' | 'cancelled'): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Maps a workspace key to a path whose shape makes the scope obvious on disk.
|
|
31
|
+
*
|
|
32
|
+
* Run-scoped paths include the run id so they are unique; durable paths deliberately do not, so
|
|
33
|
+
* the same agent (and user) reattaches to the same directory next time.
|
|
34
|
+
*/
|
|
35
|
+
private buildRelativePath;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=LocalDirectorySandboxProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocalDirectorySandboxProvider.d.ts","sourceRoot":"","sources":["../../src/sandbox/LocalDirectorySandboxProvider.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAGrG;;;;;;;;;;;;;;GAcG;AACH,qBAAa,6BAA8B,YAAW,gBAAgB;IAClE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAElC;;;;OAIG;gBACgB,QAAQ,CAAC,EAAE,MAAM;IAIpC,kBAAkB;IACL,SAAS,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;IAazF,kBAAkB;IACL,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAc1G;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;CAU5B"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { mkdir, rm } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { LogError } from '@memberjunction/core';
|
|
5
|
+
import { ChildProcessExecutor } from './ChildProcessExecutor.js';
|
|
6
|
+
/**
|
|
7
|
+
* Phase-1 sandbox: a scoped directory on the MJ server's own filesystem.
|
|
8
|
+
*
|
|
9
|
+
* ## What this does and does not protect against
|
|
10
|
+
*
|
|
11
|
+
* It gives each run a workspace of the right lifetime and keeps agents out of each other's files.
|
|
12
|
+
* It does **not** contain the harness process: a determined harness can read outside its workspace,
|
|
13
|
+
* and `NetworkPolicy` is advisory here because nothing intercepts the process's sockets. Real
|
|
14
|
+
* enforcement needs the container provider, and `mcp-only` is the recommended production posture
|
|
15
|
+
* precisely because it is the one that can actually be enforced.
|
|
16
|
+
*
|
|
17
|
+
* Saying so plainly matters more than the code: an operator who believes `networkPolicy: 'none'`
|
|
18
|
+
* is enforced by this provider has a false sense of containment, which is worse than knowing the
|
|
19
|
+
* boundary is soft.
|
|
20
|
+
*/
|
|
21
|
+
export class LocalDirectorySandboxProvider {
|
|
22
|
+
/**
|
|
23
|
+
* @param rootPath Directory under which all workspaces are created. Defaults to a folder in the
|
|
24
|
+
* OS temp dir, which is fine for `run` scope but should be pointed somewhere
|
|
25
|
+
* durable when using `agent` or `agent-user` scopes, since temp dirs get swept.
|
|
26
|
+
*/
|
|
27
|
+
constructor(rootPath) {
|
|
28
|
+
this.rootPath = rootPath ?? join(tmpdir(), 'mj-agent-harness');
|
|
29
|
+
}
|
|
30
|
+
/** @inheritdoc */
|
|
31
|
+
async Provision(key, _config) {
|
|
32
|
+
const workspacePath = join(this.rootPath, this.buildRelativePath(key));
|
|
33
|
+
await mkdir(workspacePath, { recursive: true });
|
|
34
|
+
return {
|
|
35
|
+
WorkspacePath: workspacePath,
|
|
36
|
+
Key: key,
|
|
37
|
+
Ephemeral: key.Scope === 'run',
|
|
38
|
+
// Runs directly on the MJAPI host. The workspace path is a host path here, which is the
|
|
39
|
+
// one case where it is also safe to touch with `fs` — see the note on SandboxHandle.
|
|
40
|
+
Executor: new ChildProcessExecutor(workspacePath),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** @inheritdoc */
|
|
44
|
+
async Finalize(handle, _outcome) {
|
|
45
|
+
if (!handle.Ephemeral) {
|
|
46
|
+
// Durable scopes keep their files — that is the whole point of them.
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
await rm(handle.WorkspacePath, { recursive: true, force: true });
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
// Never rethrow from finalize: it runs on failure and cancellation paths, where throwing
|
|
54
|
+
// would replace the real error with a cleanup error and lose the diagnosis.
|
|
55
|
+
LogError(`Failed to remove harness workspace ${handle.WorkspacePath}: ${e instanceof Error ? e.message : String(e)}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Maps a workspace key to a path whose shape makes the scope obvious on disk.
|
|
60
|
+
*
|
|
61
|
+
* Run-scoped paths include the run id so they are unique; durable paths deliberately do not, so
|
|
62
|
+
* the same agent (and user) reattaches to the same directory next time.
|
|
63
|
+
*/
|
|
64
|
+
buildRelativePath(key) {
|
|
65
|
+
switch (key.Scope) {
|
|
66
|
+
case 'run':
|
|
67
|
+
return join('run', key.RunId);
|
|
68
|
+
case 'agent':
|
|
69
|
+
return join('agent', key.AgentId);
|
|
70
|
+
case 'agent-user':
|
|
71
|
+
return join('agent-user', key.AgentId, key.UserId ?? 'no-user');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=LocalDirectorySandboxProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocalDirectorySandboxProvider.js","sourceRoot":"","sources":["../../src/sandbox/LocalDirectorySandboxProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEhD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEjE;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,6BAA6B;IAGtC;;;;OAIG;IACH,YAAmB,QAAiB;QAChC,IAAI,CAAC,QAAQ,GAAG,QAAQ,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC;IACnE,CAAC;IAED,kBAAkB;IACX,KAAK,CAAC,SAAS,CAAC,GAAiB,EAAE,OAAsB;QAC5D,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC;QACvE,MAAM,KAAK,CAAC,aAAa,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAChD,OAAO;YACH,aAAa,EAAE,aAAa;YAC5B,GAAG,EAAE,GAAG;YACR,SAAS,EAAE,GAAG,CAAC,KAAK,KAAK,KAAK;YAC9B,wFAAwF;YACxF,qFAAqF;YACrF,QAAQ,EAAE,IAAI,oBAAoB,CAAC,aAAa,CAAC;SACpD,CAAC;IACN,CAAC;IAED,kBAAkB;IACX,KAAK,CAAC,QAAQ,CAAC,MAAqB,EAAE,QAA6C;QACtF,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YACpB,qEAAqE;YACrE,OAAO;QACX,CAAC;QACD,IAAI,CAAC;YACD,MAAM,EAAE,CAAC,MAAM,CAAC,aAAa,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACrE,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACT,yFAAyF;YACzF,4EAA4E;YAC5E,QAAQ,CAAC,sCAAsC,MAAM,CAAC,aAAa,KAAK,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC1H,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACK,iBAAiB,CAAC,GAAiB;QACvC,QAAQ,GAAG,CAAC,KAAK,EAAE,CAAC;YAChB,KAAK,KAAK;gBACN,OAAO,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;YAClC,KAAK,OAAO;gBACR,OAAO,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;YACtC,KAAK,YAAY;gBACb,OAAO,IAAI,CAAC,YAAY,EAAE,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,IAAI,SAAS,CAAC,CAAC;QACxE,CAAC;IACL,CAAC;CACJ"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How to run one command inside a sandbox.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately NOT modelled on Node's `ChildProcess`. Spawning locally and `docker exec` both
|
|
5
|
+
* happen to produce a child process, but a Kubernetes exec is streams over a websocket and a remote
|
|
6
|
+
* runner is HTTP — an interface that promised `ChildProcess` could never be implemented honestly by
|
|
7
|
+
* either. Promising async iterables of lines instead lets every backend satisfy the same contract.
|
|
8
|
+
*
|
|
9
|
+
* The second benefit is testing: a fake executor replays canned JSONL, so adapter behaviour can be
|
|
10
|
+
* unit-tested without a harness binary, a container, or a network.
|
|
11
|
+
*/
|
|
12
|
+
export interface HarnessProcessSpec {
|
|
13
|
+
/** Binary or command to run inside the sandbox. */
|
|
14
|
+
Command: string;
|
|
15
|
+
/** Arguments, already split — never a shell string, so nothing needs quoting or escaping. */
|
|
16
|
+
Args: string[];
|
|
17
|
+
/** Environment for the process. Exactly what the agent was granted; never the host's full env. */
|
|
18
|
+
Environment: Record<string, string>;
|
|
19
|
+
/** Working directory inside the sandbox. Defaults to the workspace root. */
|
|
20
|
+
WorkingDirectory?: string;
|
|
21
|
+
/** Aborts the process. */
|
|
22
|
+
CancellationToken?: AbortSignal;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A running process inside a sandbox, wherever that sandbox physically is.
|
|
26
|
+
*/
|
|
27
|
+
export interface HarnessProcess {
|
|
28
|
+
/** stdout as complete lines, already framed. Ends when the process closes its stream. */
|
|
29
|
+
Stdout: AsyncIterable<string>;
|
|
30
|
+
/** stderr as complete lines. Consumers typically buffer this for error reporting. */
|
|
31
|
+
Stderr: AsyncIterable<string>;
|
|
32
|
+
/** Resolves with the exit code (or null if terminated by signal) once the process has exited. */
|
|
33
|
+
ExitCode: Promise<number | null>;
|
|
34
|
+
/** Terminates the process. Must be safe to call more than once, and after exit. */
|
|
35
|
+
Kill(): void;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Runs commands inside a provisioned sandbox.
|
|
39
|
+
*
|
|
40
|
+
* Obtained from {@link SandboxHandle}, so the sandbox provider — not the adapter — decides WHERE a
|
|
41
|
+
* harness process runs. That separation is the whole point: `CodexAdapter` builds argv and parses
|
|
42
|
+
* events, and never learns whether it is executing on the MJAPI host, in a container, or in a pod.
|
|
43
|
+
*/
|
|
44
|
+
export interface SandboxExecutor {
|
|
45
|
+
Run(spec: HarnessProcessSpec): HarnessProcess;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=SandboxExecutor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SandboxExecutor.d.ts","sourceRoot":"","sources":["../../src/sandbox/SandboxExecutor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,WAAW,kBAAkB;IAC/B,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,6FAA6F;IAC7F,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,kGAAkG;IAClG,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,0BAA0B;IAC1B,iBAAiB,CAAC,EAAE,WAAW,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC3B,yFAAyF;IACzF,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9B,qFAAqF;IACrF,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9B,iGAAiG;IACjG,QAAQ,EAAE,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACjC,mFAAmF;IACnF,IAAI,IAAI,IAAI,CAAC;CAChB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC5B,GAAG,CAAC,IAAI,EAAE,kBAAkB,GAAG,cAAc,CAAC;CACjD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SandboxExecutor.js","sourceRoot":"","sources":["../../src/sandbox/SandboxExecutor.ts"],"names":[],"mappings":""}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { MJAIAgentHarnessEntity } from '@memberjunction/core-entities';
|
|
2
|
+
import { SandboxExecutor } from './sandbox/SandboxExecutor.js';
|
|
3
|
+
/**
|
|
4
|
+
* What a harness adapter can do, mirrored from `AIAgentHarness.CapabilitySettings`.
|
|
5
|
+
*
|
|
6
|
+
* Derived from the entity's generated JSONType accessor rather than restated here, so this stays in
|
|
7
|
+
* lockstep with the interface CodeGen emits from `IHarnessCapabilitySettings.ts`. Restating the
|
|
8
|
+
* shape by hand is the value-list drift trap in a different costume.
|
|
9
|
+
*/
|
|
10
|
+
export type HarnessCapabilities = NonNullable<MJAIAgentHarnessEntity['CapabilitySettingsObject']>;
|
|
11
|
+
/**
|
|
12
|
+
* Everything an adapter needs to launch a harness session.
|
|
13
|
+
*
|
|
14
|
+
* Assembled once per run by {@link HarnessAgentBase} and handed to
|
|
15
|
+
* {@link BaseHarnessAdapter.StartSession}. Nothing here is re-derived per turn — a session's
|
|
16
|
+
* identity, workspace and credentials are fixed for its lifetime.
|
|
17
|
+
*/
|
|
18
|
+
export interface HarnessSessionConfig {
|
|
19
|
+
/**
|
|
20
|
+
* Runs harness processes inside the sandbox.
|
|
21
|
+
*
|
|
22
|
+
* Adapters MUST go through this rather than calling `spawn` themselves. An adapter that spawns
|
|
23
|
+
* directly always runs on the MJAPI host, which in production means an autonomous agent
|
|
24
|
+
* executing shell commands inside the API container with its network reach and cloud
|
|
25
|
+
* credentials — and, worse, it does so while the agent's config claims `provider: 'docker'`.
|
|
26
|
+
* Routing through the executor is what makes the sandbox choice real rather than decorative.
|
|
27
|
+
*/
|
|
28
|
+
Executor: SandboxExecutor;
|
|
29
|
+
/**
|
|
30
|
+
* Workspace path AS THE HARNESS SEES IT — a host path under the local provider, a
|
|
31
|
+
* container-internal path under Docker. Pass it to harness processes; do not open it with `fs`.
|
|
32
|
+
*/
|
|
33
|
+
WorkspacePath: string;
|
|
34
|
+
/**
|
|
35
|
+
* Environment variables injected into the harness process — the LLM key and any granted
|
|
36
|
+
* integration tokens resolved from `MJ: AI Agent Credentials`.
|
|
37
|
+
*
|
|
38
|
+
* This is the ONLY channel by which a secret reaches the sandbox, and it carries exactly what
|
|
39
|
+
* the agent was granted. Never DB credentials, never a user token, never a general MJ API key.
|
|
40
|
+
*/
|
|
41
|
+
Environment: Record<string, string>;
|
|
42
|
+
/** MCP endpoint for the read-only intra-turn loopback; omitted when the harness has no MCP client. */
|
|
43
|
+
McpServerUrl?: string;
|
|
44
|
+
/** Per-run, read-only, scope-limited MCP credential. Revoked at teardown on every exit path. */
|
|
45
|
+
McpCredential?: string;
|
|
46
|
+
/**
|
|
47
|
+
* A prior session this run MAY continue, when one exists for the same agent and conversation.
|
|
48
|
+
*
|
|
49
|
+
* Offered, not imposed. Only adapters whose harness can genuinely resume should act on it, and
|
|
50
|
+
* they must report the outcome through {@link BaseHarnessAdapter.DidResumeSession} — because the
|
|
51
|
+
* caller sends a DIFFERENT turn input depending on whether the resume took. Guessing wrong in
|
|
52
|
+
* either direction is costly: assume resumed when it was not and the harness has no context;
|
|
53
|
+
* assume fresh when it did resume and it receives the conversation twice.
|
|
54
|
+
*/
|
|
55
|
+
ResumeSessionId?: string;
|
|
56
|
+
/**
|
|
57
|
+
* What the agent may do inside the sandbox. Adapters translate this into their own flags via
|
|
58
|
+
* {@link BaseHarnessAdapter.ApplyPermissionPolicy}, or ignore it if they cannot enforce it.
|
|
59
|
+
*/
|
|
60
|
+
PermissionPolicy?: HarnessPermissionPolicy;
|
|
61
|
+
/** Model to request, when the harness honours one (`CapabilitySettings.ModelSelection`). */
|
|
62
|
+
Model?: string;
|
|
63
|
+
/** Aborts in-flight work; honoured mid-turn only when `CapabilitySettings.MidTurnCancellation`. */
|
|
64
|
+
CancellationToken?: AbortSignal;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Events an adapter emits while a turn is in flight.
|
|
68
|
+
*
|
|
69
|
+
* Deliberately a small, closed set. Anything the harness does INSIDE its sandbox that does not
|
|
70
|
+
* cross one of these boundaries is governed by posture policy, not recorded as a run step — the
|
|
71
|
+
* opaque-super-step property of this design, which is intentional and must stay documented rather
|
|
72
|
+
* than quietly widened.
|
|
73
|
+
*/
|
|
74
|
+
export type HarnessTurnEvent =
|
|
75
|
+
/** Streamed narration, surfaced to `onProgress` and never persisted as a step. */
|
|
76
|
+
{
|
|
77
|
+
Type: 'assistant-text';
|
|
78
|
+
Text: string;
|
|
79
|
+
}
|
|
80
|
+
/** Informational in-sandbox activity (file edit, shell command) for live view only. */
|
|
81
|
+
| {
|
|
82
|
+
Type: 'sandbox-activity';
|
|
83
|
+
Description: string;
|
|
84
|
+
}
|
|
85
|
+
/** The harness wants to do something the posture gates; becomes an `MJ: AI Agent Requests` row. */
|
|
86
|
+
| {
|
|
87
|
+
Type: 'permission-request';
|
|
88
|
+
RequestId: string;
|
|
89
|
+
Description: string;
|
|
90
|
+
Command?: string;
|
|
91
|
+
}
|
|
92
|
+
/** Token/cost usage for the turn. Without this the run cannot be accounted for — see below. */
|
|
93
|
+
| {
|
|
94
|
+
Type: 'usage';
|
|
95
|
+
InputTokens: number;
|
|
96
|
+
OutputTokens: number;
|
|
97
|
+
CostUsd?: number;
|
|
98
|
+
}
|
|
99
|
+
/** The turn ended. `RawText` is expected to carry the Loop next-step JSON envelope. */
|
|
100
|
+
| {
|
|
101
|
+
Type: 'turn-complete';
|
|
102
|
+
RawText: string;
|
|
103
|
+
}
|
|
104
|
+
/** The session failed. Terminal for the turn; the run decides whether to retry. */
|
|
105
|
+
| {
|
|
106
|
+
Type: 'session-error';
|
|
107
|
+
Error: string;
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* The accumulated outcome of one harness turn, assembled by {@link HarnessAgentBase} from the
|
|
111
|
+
* adapter's event stream.
|
|
112
|
+
*/
|
|
113
|
+
export interface HarnessTurnResult {
|
|
114
|
+
/** Raw turn-end text, handed to the Loop JSON parser exactly as a prompt response would be. */
|
|
115
|
+
RawText: string;
|
|
116
|
+
/** Summed usage for the turn. Zeros when the harness reports none — which is itself a finding. */
|
|
117
|
+
InputTokens: number;
|
|
118
|
+
OutputTokens: number;
|
|
119
|
+
CostUsd?: number;
|
|
120
|
+
/** Set when the turn failed rather than completed. */
|
|
121
|
+
ErrorMessage?: string;
|
|
122
|
+
/** Vendor session id, persisted to `AIAgentRun.ExternalSessionID` for resume and log correlation. */
|
|
123
|
+
SessionId?: string;
|
|
124
|
+
/**
|
|
125
|
+
* The model the harness ACTUALLY used, as it reported it (e.g. `claude-opus-4-6`).
|
|
126
|
+
*
|
|
127
|
+
* Distinct from the model we asked for. A harness free to pick its own model will, and recording
|
|
128
|
+
* the one we assumed instead of the one it used makes cost attribution wrong — Opus and Sonnet
|
|
129
|
+
* are not the same price.
|
|
130
|
+
*/
|
|
131
|
+
ReportedModel?: string;
|
|
132
|
+
}
|
|
133
|
+
/** Where a harness workspace lives and how long it survives. */
|
|
134
|
+
export type HarnessWorkspaceScope = 'run' | 'agent' | 'agent-user';
|
|
135
|
+
/** What the sandbox may reach on the network. */
|
|
136
|
+
export type HarnessNetworkPolicy = 'none' | 'mcp-only' | 'allowlist' | 'open';
|
|
137
|
+
/** How much in-sandbox autonomy the harness gets before MJ interposes a human. */
|
|
138
|
+
export type HarnessPosture = 'strict' | 'auto' | 'dangerous';
|
|
139
|
+
/**
|
|
140
|
+
* What the agent is permitted to do inside its sandbox, expressed in MJ's vocabulary rather than
|
|
141
|
+
* any harness's.
|
|
142
|
+
*
|
|
143
|
+
* ## Why this exists as an abstraction
|
|
144
|
+
*
|
|
145
|
+
* Every harness has its own permission mechanism and its own spelling — Claude Code has
|
|
146
|
+
* `--permission-mode` with six modes plus `--allowedTools` patterns, others have none at all. Left
|
|
147
|
+
* unabstracted, permissions would be configured per-harness, and switching harnesses would silently
|
|
148
|
+
* change what an agent may do. That is the opposite of the property this whole design exists for:
|
|
149
|
+
* MJ owns authority, the harness supplies reasoning.
|
|
150
|
+
*
|
|
151
|
+
* So the posture and tool patterns are declared ONCE in agent metadata, overridable per run, and
|
|
152
|
+
* each adapter translates them into its own flags — or ignores them and reports that it cannot
|
|
153
|
+
* enforce them.
|
|
154
|
+
*
|
|
155
|
+
* ## The postures
|
|
156
|
+
*
|
|
157
|
+
* - `strict` — nothing mutating without human approval. Honest today only where an adapter can
|
|
158
|
+
* actually intercept; where it cannot, the harness's own prompts have nowhere to go in headless
|
|
159
|
+
* mode and every tool call simply denies. That is a real, observed outcome, not a hypothetical:
|
|
160
|
+
* an agent asked to run `git status` across repos had all 21 calls blocked and correctly stopped
|
|
161
|
+
* to ask for permission MJ had no way to grant.
|
|
162
|
+
* - `auto` — the harness proceeds on its own for anything inside {@link AllowedTools}, and is
|
|
163
|
+
* refused anything in {@link DisallowedTools}. The workable default until HITL lands.
|
|
164
|
+
* - `dangerous` — no gating at all. Only defensible inside a contained sandbox; the Docker provider
|
|
165
|
+
* with a real network policy is the intended pairing, not the local provider.
|
|
166
|
+
*/
|
|
167
|
+
export interface HarnessPermissionPolicy {
|
|
168
|
+
/** How much autonomy the harness gets. See the posture notes above. */
|
|
169
|
+
Posture: HarnessPosture;
|
|
170
|
+
/**
|
|
171
|
+
* Tool patterns the agent may use without asking, in the harness's own pattern language
|
|
172
|
+
* (e.g. `Bash(git:*)`, `Read`, `Grep`). Deliberately passed through rather than normalised:
|
|
173
|
+
* inventing an MJ-wide tool taxonomy would be a lossy translation of every harness's model, and
|
|
174
|
+
* the patterns are the part operators actually reason about.
|
|
175
|
+
*/
|
|
176
|
+
AllowedTools?: string[];
|
|
177
|
+
/** Tool patterns the agent must never use. Takes precedence over {@link AllowedTools}. */
|
|
178
|
+
DisallowedTools?: string[];
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAE/D;;;;;;GAMG;AACH,MAAM,MAAM,mBAAmB,GAAG,WAAW,CAAC,sBAAsB,CAAC,0BAA0B,CAAC,CAAC,CAAC;AAElG;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACjC;;;;;;;;OAQG;IACH,QAAQ,EAAE,eAAe,CAAC;IAC1B;;;OAGG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,sGAAsG;IACtG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gGAAgG;IAChG,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;IAC3C,4FAA4F;IAC5F,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mGAAmG;IACnG,iBAAiB,CAAC,EAAE,WAAW,CAAC;CACnC;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB;AACxB,kFAAkF;AAChF;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AAC1C,uFAAuF;GACrF;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE;AACnD,mGAAmG;GACjG;IAAE,IAAI,EAAE,oBAAoB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE;AAC1F,+FAA+F;GAC7F;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE;AAChF,uFAAuF;GACrF;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;AAC5C,mFAAmF;GACjF;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/C;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAC9B,+FAA+F;IAC/F,OAAO,EAAE,MAAM,CAAC;IAChB,kGAAkG;IAClG,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qGAAqG;IACrG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,gEAAgE;AAChE,MAAM,MAAM,qBAAqB,GAAG,KAAK,GAAG,OAAO,GAAG,YAAY,CAAC;AAEnE,iDAAiD;AACjD,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,MAAM,CAAC;AAE9E,kFAAkF;AAClF,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,WAAW,uBAAuB;IACpC,uEAAuE;IACvE,OAAO,EAAE,cAAc,CAAC;IACxB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,0FAA0F;IAC1F,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,10 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ai-agent-harness",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "6.1.0-edge.2",
|
|
5
|
+
"description": "MemberJunction: External Agent Harness integration — run Claude Code, Codex, OpenCode, Gemini CLI or Pi as the reasoning substrate for an MJ agent",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"/dist"
|
|
10
|
+
],
|
|
11
|
+
"author": "MemberJunction.com",
|
|
12
|
+
"license": "ISC",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@memberjunction/ai": "6.1.0-edge.2",
|
|
15
|
+
"@memberjunction/ai-agents": "6.1.0-edge.2",
|
|
16
|
+
"@memberjunction/ai-core-plus": "6.1.0-edge.2",
|
|
17
|
+
"@memberjunction/core": "6.1.0-edge.2",
|
|
18
|
+
"@memberjunction/core-entities": "6.1.0-edge.2",
|
|
19
|
+
"@memberjunction/global": "6.1.0-edge.2",
|
|
20
|
+
"@memberjunction/templates": "6.1.0-edge.2"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "24.10.11",
|
|
24
|
+
"typescript": "^5.4.5",
|
|
25
|
+
"tsc-alias": "^1.8.10",
|
|
26
|
+
"vitest": "^4.0.18"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/MemberJunction/MJ"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc && tsc-alias -f",
|
|
34
|
+
"test": "vitest run",
|
|
35
|
+
"test:watch": "vitest"
|
|
36
|
+
}
|
|
37
|
+
}
|