@memberjunction/ai-agent-harness 0.0.0 → 6.1.0-edge.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 (71) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +192 -27
  3. package/dist/HarnessAgentBase.d.ts +254 -0
  4. package/dist/HarnessAgentBase.d.ts.map +1 -0
  5. package/dist/HarnessAgentBase.js +813 -0
  6. package/dist/HarnessAgentBase.js.map +1 -0
  7. package/dist/HarnessAgentType.d.ts +39 -0
  8. package/dist/HarnessAgentType.d.ts.map +1 -0
  9. package/dist/HarnessAgentType.js +50 -0
  10. package/dist/HarnessAgentType.js.map +1 -0
  11. package/dist/adapters/BaseCliHarnessAdapter.d.ts +93 -0
  12. package/dist/adapters/BaseCliHarnessAdapter.d.ts.map +1 -0
  13. package/dist/adapters/BaseCliHarnessAdapter.js +184 -0
  14. package/dist/adapters/BaseCliHarnessAdapter.js.map +1 -0
  15. package/dist/adapters/BaseHarnessAdapter.d.ts +114 -0
  16. package/dist/adapters/BaseHarnessAdapter.d.ts.map +1 -0
  17. package/dist/adapters/BaseHarnessAdapter.js +86 -0
  18. package/dist/adapters/BaseHarnessAdapter.js.map +1 -0
  19. package/dist/adapters/ClaudeCodeCliAdapter.d.ts +104 -0
  20. package/dist/adapters/ClaudeCodeCliAdapter.d.ts.map +1 -0
  21. package/dist/adapters/ClaudeCodeCliAdapter.js +268 -0
  22. package/dist/adapters/ClaudeCodeCliAdapter.js.map +1 -0
  23. package/dist/adapters/CodexAdapter.d.ts +27 -0
  24. package/dist/adapters/CodexAdapter.d.ts.map +1 -0
  25. package/dist/adapters/CodexAdapter.js +117 -0
  26. package/dist/adapters/CodexAdapter.js.map +1 -0
  27. package/dist/adapters/GeminiCliAdapter.d.ts +25 -0
  28. package/dist/adapters/GeminiCliAdapter.d.ts.map +1 -0
  29. package/dist/adapters/GeminiCliAdapter.js +98 -0
  30. package/dist/adapters/GeminiCliAdapter.js.map +1 -0
  31. package/dist/adapters/OpenCodeAdapter.d.ts +23 -0
  32. package/dist/adapters/OpenCodeAdapter.d.ts.map +1 -0
  33. package/dist/adapters/OpenCodeAdapter.js +104 -0
  34. package/dist/adapters/OpenCodeAdapter.js.map +1 -0
  35. package/dist/adapters/PiAdapter.d.ts +73 -0
  36. package/dist/adapters/PiAdapter.d.ts.map +1 -0
  37. package/dist/adapters/PiAdapter.js +237 -0
  38. package/dist/adapters/PiAdapter.js.map +1 -0
  39. package/dist/adapters/StdioJsonAdapter.d.ts +43 -0
  40. package/dist/adapters/StdioJsonAdapter.d.ts.map +1 -0
  41. package/dist/adapters/StdioJsonAdapter.js +109 -0
  42. package/dist/adapters/StdioJsonAdapter.js.map +1 -0
  43. package/dist/index.d.ts +26 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +28 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/sandbox/ChildProcessExecutor.d.ts +41 -0
  48. package/dist/sandbox/ChildProcessExecutor.d.ts.map +1 -0
  49. package/dist/sandbox/ChildProcessExecutor.js +86 -0
  50. package/dist/sandbox/ChildProcessExecutor.js.map +1 -0
  51. package/dist/sandbox/DockerSandboxProvider.d.ts +64 -0
  52. package/dist/sandbox/DockerSandboxProvider.d.ts.map +1 -0
  53. package/dist/sandbox/DockerSandboxProvider.js +176 -0
  54. package/dist/sandbox/DockerSandboxProvider.js.map +1 -0
  55. package/dist/sandbox/ISandboxProvider.d.ts +71 -0
  56. package/dist/sandbox/ISandboxProvider.d.ts.map +1 -0
  57. package/dist/sandbox/ISandboxProvider.js +2 -0
  58. package/dist/sandbox/ISandboxProvider.js.map +1 -0
  59. package/dist/sandbox/LocalDirectorySandboxProvider.d.ts +37 -0
  60. package/dist/sandbox/LocalDirectorySandboxProvider.d.ts.map +1 -0
  61. package/dist/sandbox/LocalDirectorySandboxProvider.js +75 -0
  62. package/dist/sandbox/LocalDirectorySandboxProvider.js.map +1 -0
  63. package/dist/sandbox/SandboxExecutor.d.ts +47 -0
  64. package/dist/sandbox/SandboxExecutor.d.ts.map +1 -0
  65. package/dist/sandbox/SandboxExecutor.js +2 -0
  66. package/dist/sandbox/SandboxExecutor.js.map +1 -0
  67. package/dist/types.d.ts +180 -0
  68. package/dist/types.d.ts.map +1 -0
  69. package/dist/types.js +2 -0
  70. package/dist/types.js.map +1 -0
  71. package/package.json +35 -8
@@ -0,0 +1,109 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { RegisterClass } from '@memberjunction/global';
8
+ import { BaseCliHarnessAdapter } from './BaseCliHarnessAdapter.js';
9
+ import { BaseHarnessAdapter } from './BaseHarnessAdapter.js';
10
+ /**
11
+ * Drives ANY command that speaks a documented newline-delimited JSON event contract.
12
+ *
13
+ * This is the escape hatch that keeps "adding a harness must not require core changes" true in the
14
+ * strong sense: a harness MJ has never heard of works with zero MJ code, provided its CLI emits
15
+ * lines matching the vocabulary below.
16
+ *
17
+ * ## The contract
18
+ *
19
+ * Each stdout line is a JSON object with a `type` field:
20
+ *
21
+ * | `type` | Other fields | Meaning |
22
+ * |--------------|---------------------------------------|------------------------------------------|
23
+ * | `text` | `text` | Narration; streamed, never persisted |
24
+ * | `activity` | `description` | In-sandbox activity, for live view only |
25
+ * | `permission` | `id`, `description`, `command?` | Requests approval; becomes a HITL row |
26
+ * | `usage` | `input_tokens`, `output_tokens`, `cost_usd?` | Turn usage; drives cost guardrails |
27
+ * | `complete` | `text` | Turn ended; `text` carries the envelope |
28
+ * | `error` | `message` | Turn failed |
29
+ *
30
+ * Unrecognised lines are ignored, so a harness may interleave its own diagnostics freely.
31
+ *
32
+ * Capabilities default to the conservative end — an unknown harness is assumed NOT to resume
33
+ * sessions or honour permission hooks, so the runtime emulates continuity rather than silently
34
+ * losing context. Override via `AIAgentHarness.CapabilitySettings` when the harness does better.
35
+ */
36
+ let StdioJsonAdapter = class StdioJsonAdapter extends BaseCliHarnessAdapter {
37
+ constructor() {
38
+ super(...arguments);
39
+ this.executable = 'harness';
40
+ this.extraArgs = [];
41
+ this.declaredCapabilities = null;
42
+ }
43
+ get ExecutablePath() {
44
+ return this.executable;
45
+ }
46
+ /** @inheritdoc */
47
+ get Capabilities() {
48
+ return (this.declaredCapabilities ?? {
49
+ SessionResume: false,
50
+ StructuredOutput: false,
51
+ UsageReporting: true,
52
+ // FALSE by definition: this is the generic escape hatch for a harness with no
53
+ // first-class adapter, so there is no known flag vocabulary to translate a policy
54
+ // into. A subclass that knows its CLI should override ApplyPermissionPolicy and
55
+ // report true.
56
+ PermissionPolicy: false,
57
+ PermissionHooks: false,
58
+ McpClient: false,
59
+ WorkspaceScoping: true,
60
+ ModelSelection: false,
61
+ });
62
+ }
63
+ BuildTurnArgs(input, _isFirstTurn) {
64
+ return [...this.extraArgs, input];
65
+ }
66
+ MapEvent(raw) {
67
+ switch (this.readString(raw, 'type')) {
68
+ case 'text':
69
+ return { Type: 'assistant-text', Text: this.readString(raw, 'text') ?? '' };
70
+ case 'activity':
71
+ return { Type: 'sandbox-activity', Description: this.readString(raw, 'description') ?? 'activity' };
72
+ case 'permission':
73
+ return {
74
+ Type: 'permission-request',
75
+ RequestId: this.readString(raw, 'id') ?? '',
76
+ Description: this.readString(raw, 'description') ?? 'permission requested',
77
+ Command: this.readString(raw, 'command'),
78
+ };
79
+ case 'usage':
80
+ return {
81
+ Type: 'usage',
82
+ InputTokens: this.readNumber(raw, 'input_tokens') ?? 0,
83
+ OutputTokens: this.readNumber(raw, 'output_tokens') ?? 0,
84
+ CostUsd: this.readNumber(raw, 'cost_usd'),
85
+ };
86
+ case 'complete':
87
+ return { Type: 'turn-complete', RawText: this.readString(raw, 'text') ?? '' };
88
+ case 'error':
89
+ return { Type: 'session-error', Error: this.readString(raw, 'message') ?? 'harness reported an error' };
90
+ default:
91
+ return null;
92
+ }
93
+ }
94
+ /** Configures the command, extra argv and declared capabilities from `AIAgentHarness` metadata. */
95
+ Configure(executable, extraArgs, capabilities) {
96
+ this.executable = executable;
97
+ this.extraArgs = extraArgs;
98
+ this.declaredCapabilities = capabilities;
99
+ }
100
+ /** Points the adapter at a specific binary, from `AIAgentHarness.ExecutablePath`. */
101
+ SetExecutable(path) {
102
+ this.executable = path;
103
+ }
104
+ };
105
+ StdioJsonAdapter = __decorate([
106
+ RegisterClass(BaseHarnessAdapter, 'StdioJsonAdapter')
107
+ ], StdioJsonAdapter);
108
+ export { StdioJsonAdapter };
109
+ //# sourceMappingURL=StdioJsonAdapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StdioJsonAdapter.js","sourceRoot":"","sources":["../../src/adapters/StdioJsonAdapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ export * from './types.js';
2
+ export * from './HarnessAgentType.js';
3
+ export * from './HarnessAgentBase.js';
4
+ export * from './adapters/BaseHarnessAdapter.js';
5
+ export * from './adapters/BaseCliHarnessAdapter.js';
6
+ export * from './adapters/StdioJsonAdapter.js';
7
+ export * from './adapters/ClaudeCodeCliAdapter.js';
8
+ export * from './adapters/CodexAdapter.js';
9
+ export * from './adapters/OpenCodeAdapter.js';
10
+ export * from './adapters/GeminiCliAdapter.js';
11
+ export * from './adapters/PiAdapter.js';
12
+ export * from './sandbox/SandboxExecutor.js';
13
+ export * from './sandbox/ISandboxProvider.js';
14
+ export * from './sandbox/ChildProcessExecutor.js';
15
+ export * from './sandbox/DockerSandboxProvider.js';
16
+ export * from './sandbox/LocalDirectorySandboxProvider.js';
17
+ /**
18
+ * Tree-shaking guard.
19
+ *
20
+ * The adapters register themselves with ClassFactory via `@RegisterClass` as a side effect of being
21
+ * loaded. A bundler that sees no direct import of a module is free to drop it, which would leave
22
+ * `AIAgentHarness.DriverClass` resolving to nothing at runtime — a failure that only appears in a
23
+ * built artifact, never in dev. Calling this from a consumer's startup path keeps them reachable.
24
+ */
25
+ export declare function LoadAgentHarnessAdapters(): void;
26
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kCAAkC,CAAC;AACjD,cAAc,qCAAqC,CAAC;AACpD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,oCAAoC,CAAC;AACnD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,yBAAyB,CAAC;AACxC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,4CAA4C,CAAC;AAE3D;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C"}
package/dist/index.js ADDED
@@ -0,0 +1,28 @@
1
+ export * from './types.js';
2
+ export * from './HarnessAgentType.js';
3
+ export * from './HarnessAgentBase.js';
4
+ export * from './adapters/BaseHarnessAdapter.js';
5
+ export * from './adapters/BaseCliHarnessAdapter.js';
6
+ export * from './adapters/StdioJsonAdapter.js';
7
+ export * from './adapters/ClaudeCodeCliAdapter.js';
8
+ export * from './adapters/CodexAdapter.js';
9
+ export * from './adapters/OpenCodeAdapter.js';
10
+ export * from './adapters/GeminiCliAdapter.js';
11
+ export * from './adapters/PiAdapter.js';
12
+ export * from './sandbox/SandboxExecutor.js';
13
+ export * from './sandbox/ISandboxProvider.js';
14
+ export * from './sandbox/ChildProcessExecutor.js';
15
+ export * from './sandbox/DockerSandboxProvider.js';
16
+ export * from './sandbox/LocalDirectorySandboxProvider.js';
17
+ /**
18
+ * Tree-shaking guard.
19
+ *
20
+ * The adapters register themselves with ClassFactory via `@RegisterClass` as a side effect of being
21
+ * loaded. A bundler that sees no direct import of a module is free to drop it, which would leave
22
+ * `AIAgentHarness.DriverClass` resolving to nothing at runtime — a failure that only appears in a
23
+ * built artifact, never in dev. Calling this from a consumer's startup path keeps them reachable.
24
+ */
25
+ export function LoadAgentHarnessAdapters() {
26
+ // Intentionally empty — the imports above are the point.
27
+ }
28
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kCAAkC,CAAC;AACjD,cAAc,qCAAqC,CAAC;AACpD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,oCAAoC,CAAC;AACnD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,yBAAyB,CAAC;AACxC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,4CAA4C,CAAC;AAE3D;;;;;;;GAOG;AACH,MAAM,UAAU,wBAAwB;IACpC,yDAAyD;AAC7D,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { ChildProcessWithoutNullStreams } from 'node:child_process';
2
+ import { HarnessProcess, HarnessProcessSpec, SandboxExecutor } from './SandboxExecutor.js';
3
+ /**
4
+ * Turns a spawned child process into the backend-neutral {@link HarnessProcess} shape.
5
+ *
6
+ * Shared by every executor that ultimately runs a local binary — the local provider spawns the
7
+ * harness itself, and the Docker provider spawns `docker exec`. Only the argv differs, so the
8
+ * process plumbing lives here once rather than in each provider.
9
+ */
10
+ export declare function wrapChildProcess(child: ChildProcessWithoutNullStreams): HarnessProcess;
11
+ /**
12
+ * Runs harness processes directly on the MJAPI host.
13
+ *
14
+ * Provides no isolation whatsoever — see {@link LocalDirectorySandboxProvider} for what that means
15
+ * and why it is a development-only posture.
16
+ */
17
+ export declare class ChildProcessExecutor implements SandboxExecutor {
18
+ private readonly defaultWorkingDirectory;
19
+ constructor(defaultWorkingDirectory: string);
20
+ /**
21
+ * The host variables a locally-spawned harness inherits, before granted credentials are layered
22
+ * on top.
23
+ *
24
+ * Deliberately an ALLOWLIST, not the full process environment: passing everything through would
25
+ * hand the harness whatever credentials the MJAPI process happens to hold, which is exactly the
26
+ * over-granting the credential model exists to prevent.
27
+ *
28
+ * `HOME` is on the list for a specific reason. Local CLI harnesses keep their own login state
29
+ * under the user's home directory (Claude Code in `~/.claude`), so a developer who has already
30
+ * authenticated their CLI can run a harness agent with no credential row and no API key at all —
31
+ * the "true local" mode. Without HOME the harness cannot find its session and reports "Not
32
+ * logged in", which reads as a broken integration rather than a stripped variable.
33
+ *
34
+ * This applies ONLY to local execution. The Docker executor passes just the granted environment,
35
+ * because a container has no business inheriting the host developer's identity.
36
+ */
37
+ private baseEnvironment;
38
+ /** @inheritdoc */
39
+ Run(spec: HarnessProcessSpec): HarnessProcess;
40
+ }
41
+ //# sourceMappingURL=ChildProcessExecutor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChildProcessExecutor.d.ts","sourceRoot":"","sources":["../../src/sandbox/ChildProcessExecutor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,8BAA8B,EAAS,MAAM,oBAAoB,CAAC;AAG3E,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE3F;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,8BAA8B,GAAG,cAAc,CAoBtF;AAUD;;;;;GAKG;AACH,qBAAa,oBAAqB,YAAW,eAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,uBAAuB;gBAAvB,uBAAuB,EAAE,MAAM;IAEnE;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,eAAe;IAYvB,kBAAkB;IACX,GAAG,CAAC,IAAI,EAAE,kBAAkB,GAAG,cAAc;CAQvD"}
@@ -0,0 +1,86 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { createInterface } from 'node:readline';
3
+ /**
4
+ * Turns a spawned child process into the backend-neutral {@link HarnessProcess} shape.
5
+ *
6
+ * Shared by every executor that ultimately runs a local binary — the local provider spawns the
7
+ * harness itself, and the Docker provider spawns `docker exec`. Only the argv differs, so the
8
+ * process plumbing lives here once rather than in each provider.
9
+ */
10
+ export function wrapChildProcess(child) {
11
+ return {
12
+ Stdout: readLines(child.stdout),
13
+ Stderr: readLines(child.stderr),
14
+ ExitCode: new Promise((resolve) => {
15
+ if (child.exitCode !== null) {
16
+ resolve(child.exitCode);
17
+ return;
18
+ }
19
+ child.once('close', (code) => resolve(code));
20
+ // A spawn failure (ENOENT for a missing binary) emits 'error' and never 'close', so
21
+ // without this the ExitCode promise would hang forever and take the turn with it.
22
+ child.once('error', () => resolve(null));
23
+ }),
24
+ Kill: () => {
25
+ if (child.exitCode === null && !child.killed) {
26
+ child.kill();
27
+ }
28
+ },
29
+ };
30
+ }
31
+ /** Frames a stream into complete lines. */
32
+ async function* readLines(stream) {
33
+ const lines = createInterface({ input: stream, crlfDelay: Infinity });
34
+ for await (const line of lines) {
35
+ yield line;
36
+ }
37
+ }
38
+ /**
39
+ * Runs harness processes directly on the MJAPI host.
40
+ *
41
+ * Provides no isolation whatsoever — see {@link LocalDirectorySandboxProvider} for what that means
42
+ * and why it is a development-only posture.
43
+ */
44
+ export class ChildProcessExecutor {
45
+ constructor(defaultWorkingDirectory) {
46
+ this.defaultWorkingDirectory = defaultWorkingDirectory;
47
+ }
48
+ /**
49
+ * The host variables a locally-spawned harness inherits, before granted credentials are layered
50
+ * on top.
51
+ *
52
+ * Deliberately an ALLOWLIST, not the full process environment: passing everything through would
53
+ * hand the harness whatever credentials the MJAPI process happens to hold, which is exactly the
54
+ * over-granting the credential model exists to prevent.
55
+ *
56
+ * `HOME` is on the list for a specific reason. Local CLI harnesses keep their own login state
57
+ * under the user's home directory (Claude Code in `~/.claude`), so a developer who has already
58
+ * authenticated their CLI can run a harness agent with no credential row and no API key at all —
59
+ * the "true local" mode. Without HOME the harness cannot find its session and reports "Not
60
+ * logged in", which reads as a broken integration rather than a stripped variable.
61
+ *
62
+ * This applies ONLY to local execution. The Docker executor passes just the granted environment,
63
+ * because a container has no business inheriting the host developer's identity.
64
+ */
65
+ baseEnvironment() {
66
+ const allowed = ['PATH', 'HOME', 'USER', 'LOGNAME', 'SHELL', 'TMPDIR', 'LANG', 'TERM'];
67
+ const env = {};
68
+ for (const key of allowed) {
69
+ const value = process.env[key];
70
+ if (value !== undefined) {
71
+ env[key] = value;
72
+ }
73
+ }
74
+ return env;
75
+ }
76
+ /** @inheritdoc */
77
+ Run(spec) {
78
+ const child = spawn(spec.Command, spec.Args, {
79
+ cwd: spec.WorkingDirectory ?? this.defaultWorkingDirectory,
80
+ env: { ...this.baseEnvironment(), ...spec.Environment },
81
+ signal: spec.CancellationToken,
82
+ });
83
+ return wrapChildProcess(child);
84
+ }
85
+ }
86
+ //# sourceMappingURL=ChildProcessExecutor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChildProcessExecutor.js","sourceRoot":"","sources":["../../src/sandbox/ChildProcessExecutor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3E,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGhD;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAqC;IAClE,OAAO;QACH,MAAM,EAAE,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC;QAC/B,MAAM,EAAE,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC;QAC/B,QAAQ,EAAE,IAAI,OAAO,CAAgB,CAAC,OAAO,EAAE,EAAE;YAC7C,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;gBAC1B,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;gBACxB,OAAO;YACX,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;YAC7C,oFAAoF;YACpF,kFAAkF;YAClF,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAC7C,CAAC,CAAC;QACF,IAAI,EAAE,GAAG,EAAE;YACP,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;gBAC3C,KAAK,CAAC,IAAI,EAAE,CAAC;YACjB,CAAC;QACL,CAAC;KACJ,CAAC;AACN,CAAC;AAED,2CAA2C;AAC3C,KAAK,SAAS,CAAC,CAAC,SAAS,CAAC,MAAgB;IACtC,MAAM,KAAK,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;IACtE,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC7B,MAAM,IAAI,CAAC;IACf,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,OAAO,oBAAoB;IAC7B,YAAoC,uBAA+B;QAA/B,4BAAuB,GAAvB,uBAAuB,CAAQ;IAAG,CAAC;IAEvE;;;;;;;;;;;;;;;;OAgBG;IACK,eAAe;QACnB,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QACvF,MAAM,GAAG,GAA2B,EAAE,CAAC;QACvC,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YACxB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC/B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACtB,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YACrB,CAAC;QACL,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IAED,kBAAkB;IACX,GAAG,CAAC,IAAwB;QAC/B,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,EAAE;YACzC,GAAG,EAAE,IAAI,CAAC,gBAAgB,IAAI,IAAI,CAAC,uBAAuB;YAC1D,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC,eAAe,EAAE,EAAE,GAAG,IAAI,CAAC,WAAW,EAAE;YACvD,MAAM,EAAE,IAAI,CAAC,iBAAiB;SACjC,CAAC,CAAC;QACH,OAAO,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;CACJ"}
@@ -0,0 +1,64 @@
1
+ import { ISandboxProvider, SandboxConfig, SandboxHandle, WorkspaceKey } from './ISandboxProvider.js';
2
+ import { HarnessProcess, HarnessProcessSpec, SandboxExecutor } from './SandboxExecutor.js';
3
+ /**
4
+ * Runs each harness turn inside a per-run container.
5
+ *
6
+ * ## Why this exists
7
+ *
8
+ * {@link LocalDirectorySandboxProvider} scopes a directory but does not contain the process: the
9
+ * harness runs on the MJAPI host with that host's network reach and cloud credentials. For a feature
10
+ * whose entire purpose is executing an autonomous agent's shell commands, that is the wrong blast
11
+ * radius anywhere but a developer's laptop.
12
+ *
13
+ * Here the harness runs in a container with the workspace bind-mounted, so a file write outside the
14
+ * workspace hits the container's filesystem and dies with it, and `networkPolicy` is enforced by
15
+ * Docker rather than merely documented.
16
+ *
17
+ * ## Container per RUN, exec per TURN
18
+ *
19
+ * The container starts once at {@link Provision} and every turn is a `docker exec` into it. The
20
+ * alternative — `docker run` per turn — would pay container startup on every turn and, worse, lose
21
+ * any in-container state the harness accumulated outside the mounted workspace. A run is the natural
22
+ * lifetime because it is exactly the span over which a harness session is continuous.
23
+ *
24
+ * ## Network policy
25
+ *
26
+ * `none` maps to `--network none`. `mcp-only` and `allowlist` currently map to a bridge network and
27
+ * are NOT yet enforced at the packet level — enforcing them properly needs a per-run network with
28
+ * egress rules, which is the next increment. They are documented here as not-yet-enforced rather
29
+ * than quietly treated as equivalent to `open`, because an operator who believes `mcp-only` is
30
+ * enforced has a false sense of containment, which is worse than knowing the boundary is soft.
31
+ */
32
+ export declare class DockerSandboxProvider implements ISandboxProvider {
33
+ private readonly hostRootPath;
34
+ private readonly defaultImage;
35
+ private readonly containers;
36
+ constructor(options?: {
37
+ hostRootPath?: string;
38
+ defaultImage?: string;
39
+ });
40
+ /** @inheritdoc */
41
+ Provision(key: WorkspaceKey, config: SandboxConfig): Promise<SandboxHandle>;
42
+ /** @inheritdoc */
43
+ Finalize(handle: SandboxHandle, _outcome: 'success' | 'failure' | 'cancelled'): Promise<void>;
44
+ /** Maps the declared network policy onto docker flags, honestly. */
45
+ private buildNetworkArgs;
46
+ /** Runs a docker CLI command and resolves with its trimmed stdout. */
47
+ private runDockerCommand;
48
+ /** Same shape as the local provider, so a workspace is recognisable across both. */
49
+ private buildRelativePath;
50
+ }
51
+ /**
52
+ * Runs harness processes via `docker exec` into an already-running per-run container.
53
+ *
54
+ * Environment is passed with repeated `--env` flags rather than baked into the container at start,
55
+ * so a credential rotated between turns takes effect on the next turn without recreating the
56
+ * sandbox.
57
+ */
58
+ export declare class DockerExecExecutor implements SandboxExecutor {
59
+ private readonly containerName;
60
+ constructor(containerName: string);
61
+ /** @inheritdoc */
62
+ Run(spec: HarnessProcessSpec): HarnessProcess;
63
+ }
64
+ //# sourceMappingURL=DockerSandboxProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DockerSandboxProvider.d.ts","sourceRoot":"","sources":["../../src/sandbox/DockerSandboxProvider.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrG,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAO3F;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,qBAAsB,YAAW,gBAAgB;IAC1D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA6B;gBAErC,OAAO,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE;IAK7E,kBAAkB;IACL,SAAS,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;IAsCxF,kBAAkB;IACL,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAsB1G,oEAAoE;IACpE,OAAO,CAAC,gBAAgB;IAexB,sEAAsE;IACtE,OAAO,CAAC,gBAAgB;IAkBxB,oFAAoF;IACpF,OAAO,CAAC,iBAAiB;CAU5B;AAED;;;;;;GAMG;AACH,qBAAa,kBAAmB,YAAW,eAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,aAAa;gBAAb,aAAa,EAAE,MAAM;IAEzD,kBAAkB;IACX,GAAG,CAAC,IAAI,EAAE,kBAAkB,GAAG,cAAc;CAavD"}
@@ -0,0 +1,176 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { mkdir, rm } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ import { LogError, LogStatus } from '@memberjunction/core';
6
+ import { wrapChildProcess } from './ChildProcessExecutor.js';
7
+ /** Path the host workspace is mounted at inside the container. */
8
+ const CONTAINER_WORKSPACE = '/workspace';
9
+ /**
10
+ * Runs each harness turn inside a per-run container.
11
+ *
12
+ * ## Why this exists
13
+ *
14
+ * {@link LocalDirectorySandboxProvider} scopes a directory but does not contain the process: the
15
+ * harness runs on the MJAPI host with that host's network reach and cloud credentials. For a feature
16
+ * whose entire purpose is executing an autonomous agent's shell commands, that is the wrong blast
17
+ * radius anywhere but a developer's laptop.
18
+ *
19
+ * Here the harness runs in a container with the workspace bind-mounted, so a file write outside the
20
+ * workspace hits the container's filesystem and dies with it, and `networkPolicy` is enforced by
21
+ * Docker rather than merely documented.
22
+ *
23
+ * ## Container per RUN, exec per TURN
24
+ *
25
+ * The container starts once at {@link Provision} and every turn is a `docker exec` into it. The
26
+ * alternative — `docker run` per turn — would pay container startup on every turn and, worse, lose
27
+ * any in-container state the harness accumulated outside the mounted workspace. A run is the natural
28
+ * lifetime because it is exactly the span over which a harness session is continuous.
29
+ *
30
+ * ## Network policy
31
+ *
32
+ * `none` maps to `--network none`. `mcp-only` and `allowlist` currently map to a bridge network and
33
+ * are NOT yet enforced at the packet level — enforcing them properly needs a per-run network with
34
+ * egress rules, which is the next increment. They are documented here as not-yet-enforced rather
35
+ * than quietly treated as equivalent to `open`, because an operator who believes `mcp-only` is
36
+ * enforced has a false sense of containment, which is worse than knowing the boundary is soft.
37
+ */
38
+ export class DockerSandboxProvider {
39
+ constructor(options) {
40
+ this.containers = new Map();
41
+ this.hostRootPath = options?.hostRootPath ?? join(tmpdir(), 'mj-agent-harness');
42
+ this.defaultImage = options?.defaultImage ?? 'ghcr.io/memberjunction/harness-sandbox:latest';
43
+ }
44
+ /** @inheritdoc */
45
+ async Provision(key, config) {
46
+ const hostPath = join(this.hostRootPath, this.buildRelativePath(key));
47
+ await mkdir(hostPath, { recursive: true });
48
+ const image = config.Image ?? this.defaultImage;
49
+ const containerName = `mj-harness-${key.RunId}`;
50
+ const args = [
51
+ 'run',
52
+ '--detach',
53
+ '--rm',
54
+ '--name',
55
+ containerName,
56
+ '--volume',
57
+ `${hostPath}:${CONTAINER_WORKSPACE}`,
58
+ '--workdir',
59
+ CONTAINER_WORKSPACE,
60
+ ...this.buildNetworkArgs(config.NetworkPolicy),
61
+ image,
62
+ // Keep the container alive so turns can exec into it; the harness itself is never this
63
+ // process, it is whatever `docker exec` runs.
64
+ 'sleep',
65
+ 'infinity',
66
+ ];
67
+ const containerId = await this.runDockerCommand(args);
68
+ this.containers.set(containerName, containerId);
69
+ LogStatus(`Harness container started: ${containerName} (${image})`);
70
+ return {
71
+ // The path AS THE HARNESS SEES IT — inside the container, not on the host. Anything that
72
+ // tries to open this with `fs` on the MJAPI host is wrong; see SandboxHandle's note.
73
+ WorkspacePath: CONTAINER_WORKSPACE,
74
+ Key: key,
75
+ Ephemeral: key.Scope === 'run',
76
+ Executor: new DockerExecExecutor(containerName),
77
+ };
78
+ }
79
+ /** @inheritdoc */
80
+ async Finalize(handle, _outcome) {
81
+ const containerName = `mj-harness-${handle.Key.RunId}`;
82
+ try {
83
+ // --rm on the container means stopping it removes it, so this is both stop and cleanup.
84
+ await this.runDockerCommand(['stop', '--time', '5', containerName]);
85
+ }
86
+ catch (e) {
87
+ // Never rethrow from finalize: it runs on failure and cancellation paths, where throwing
88
+ // would replace the real error with a cleanup error and lose the diagnosis.
89
+ LogError(`Failed to stop harness container ${containerName}: ${describeError(e)}`);
90
+ }
91
+ this.containers.delete(containerName);
92
+ if (handle.Ephemeral) {
93
+ const hostPath = join(this.hostRootPath, this.buildRelativePath(handle.Key));
94
+ try {
95
+ await rm(hostPath, { recursive: true, force: true });
96
+ }
97
+ catch (e) {
98
+ LogError(`Failed to remove harness workspace ${hostPath}: ${describeError(e)}`);
99
+ }
100
+ }
101
+ }
102
+ /** Maps the declared network policy onto docker flags, honestly. */
103
+ buildNetworkArgs(policy) {
104
+ switch (policy) {
105
+ case 'none':
106
+ // Fully enforced: no interfaces at all.
107
+ return ['--network', 'none'];
108
+ case 'mcp-only':
109
+ case 'allowlist':
110
+ // NOT yet enforced at the packet level — see the class doc. Deliberately identical
111
+ // to 'open' today rather than pretending otherwise.
112
+ return [];
113
+ case 'open':
114
+ return [];
115
+ }
116
+ }
117
+ /** Runs a docker CLI command and resolves with its trimmed stdout. */
118
+ runDockerCommand(args) {
119
+ return new Promise((resolve, reject) => {
120
+ const child = spawn('docker', args);
121
+ const out = [];
122
+ const err = [];
123
+ child.stdout.on('data', (c) => out.push(c.toString()));
124
+ child.stderr.on('data', (c) => err.push(c.toString()));
125
+ child.once('error', (e) => reject(e));
126
+ child.once('close', (code) => {
127
+ if (code === 0) {
128
+ resolve(out.join('').trim());
129
+ }
130
+ else {
131
+ reject(new Error(`docker ${args[0]} failed (code ${code}): ${err.join('').trim()}`));
132
+ }
133
+ });
134
+ });
135
+ }
136
+ /** Same shape as the local provider, so a workspace is recognisable across both. */
137
+ buildRelativePath(key) {
138
+ switch (key.Scope) {
139
+ case 'run':
140
+ return join('run', key.RunId);
141
+ case 'agent':
142
+ return join('agent', key.AgentId);
143
+ case 'agent-user':
144
+ return join('agent-user', key.AgentId, key.UserId ?? 'no-user');
145
+ }
146
+ }
147
+ }
148
+ /**
149
+ * Runs harness processes via `docker exec` into an already-running per-run container.
150
+ *
151
+ * Environment is passed with repeated `--env` flags rather than baked into the container at start,
152
+ * so a credential rotated between turns takes effect on the next turn without recreating the
153
+ * sandbox.
154
+ */
155
+ export class DockerExecExecutor {
156
+ constructor(containerName) {
157
+ this.containerName = containerName;
158
+ }
159
+ /** @inheritdoc */
160
+ Run(spec) {
161
+ const args = ['exec', '--interactive'];
162
+ for (const [key, value] of Object.entries(spec.Environment)) {
163
+ args.push('--env', `${key}=${value}`);
164
+ }
165
+ if (spec.WorkingDirectory) {
166
+ args.push('--workdir', spec.WorkingDirectory);
167
+ }
168
+ args.push(this.containerName, spec.Command, ...spec.Args);
169
+ const child = spawn('docker', args, { signal: spec.CancellationToken });
170
+ return wrapChildProcess(child);
171
+ }
172
+ }
173
+ function describeError(e) {
174
+ return e instanceof Error ? e.message : String(e);
175
+ }
176
+ //# sourceMappingURL=DockerSandboxProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DockerSandboxProvider.js","sourceRoot":"","sources":["../../src/sandbox/DockerSandboxProvider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ import { HarnessNetworkPolicy, HarnessWorkspaceScope } from '../types.js';
2
+ import { SandboxExecutor } from './SandboxExecutor.js';
3
+ /**
4
+ * Identifies which workspace a run should get, and therefore how long its files live.
5
+ *
6
+ * Scope is the deciding field: `run` means a fresh directory nobody sees again, `agent` a shared
7
+ * workspace across every run of that agent, `agent-user` one per agent per user — the default,
8
+ * because it gives an agent continuity without letting one user's working files leak into another's
9
+ * session.
10
+ */
11
+ export interface WorkspaceKey {
12
+ Scope: HarnessWorkspaceScope;
13
+ AgentId: string;
14
+ UserId?: string;
15
+ RunId: string;
16
+ }
17
+ /** Runtime knobs a provider honours when provisioning. */
18
+ export interface SandboxConfig {
19
+ NetworkPolicy: HarnessNetworkPolicy;
20
+ /** Container image, for providers that have one. Ignored by the local provider. */
21
+ Image?: string;
22
+ /** How to handle two runs wanting the same durable workspace. */
23
+ Concurrency?: 'queue' | 'fail' | 'fork';
24
+ }
25
+ /** A provisioned workspace, and the means of running commands inside it. */
26
+ export interface SandboxHandle {
27
+ /**
28
+ * Workspace path AS THE HARNESS SEES IT.
29
+ *
30
+ * For the local provider that is a host path. For a container provider it is the path inside the
31
+ * container, which is generally NOT the host path the workspace was created at — the two are
32
+ * connected by a mount the provider set up. Code that passes this to a harness process is
33
+ * correct; code that opens it with `fs` on the MJAPI host is only correct for the local provider,
34
+ * and that distinction is why the field is documented rather than just named.
35
+ */
36
+ WorkspacePath: string;
37
+ Key: WorkspaceKey;
38
+ /** True when the workspace is discarded on finalize rather than retained for the next run. */
39
+ Ephemeral: boolean;
40
+ /**
41
+ * Runs harness processes inside this sandbox.
42
+ *
43
+ * The reason process placement lives on the HANDLE rather than in the adapter: an adapter that
44
+ * called `spawn()` directly would always run on the MJAPI host, which in production means an
45
+ * autonomous agent executing shell commands inside the API container with its network reach and
46
+ * cloud credentials. Routing every process through the provider's executor is what makes
47
+ * `provider: 'docker'` mean something.
48
+ */
49
+ Executor: SandboxExecutor;
50
+ }
51
+ /**
52
+ * Provisions and finalizes the filesystem a harness runs against.
53
+ *
54
+ * Kept deliberately small — two methods — because the interesting variation between a local
55
+ * directory and a container is not the shape of the API, it is what `NetworkPolicy` can actually
56
+ * enforce. The local provider can only honour it on a best-effort basis; a container provider
57
+ * enforces it for real, which is why `mcp-only` is the recommended production posture and only
58
+ * meaningful there.
59
+ */
60
+ export interface ISandboxProvider {
61
+ /** Provisions (or reattaches to) the workspace identified by `key`. */
62
+ Provision(key: WorkspaceKey, config: SandboxConfig): Promise<SandboxHandle>;
63
+ /**
64
+ * Releases the workspace.
65
+ *
66
+ * Must be safe to call on every exit path including crash and cancellation, and must not throw —
67
+ * a finalize that throws inside a failure path masks the original error with a cleanup error.
68
+ */
69
+ Finalize(handle: SandboxHandle, outcome: 'success' | 'failure' | 'cancelled'): Promise<void>;
70
+ }
71
+ //# sourceMappingURL=ISandboxProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ISandboxProvider.d.ts","sourceRoot":"","sources":["../../src/sandbox/ISandboxProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IACzB,KAAK,EAAE,qBAAqB,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,0DAA0D;AAC1D,MAAM,WAAW,aAAa;IAC1B,aAAa,EAAE,oBAAoB,CAAC;IACpC,mFAAmF;IACnF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;CAC3C;AAED,4EAA4E;AAC5E,MAAM,WAAW,aAAa;IAC1B;;;;;;;;OAQG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,GAAG,EAAE,YAAY,CAAC;IAClB,8FAA8F;IAC9F,SAAS,EAAE,OAAO,CAAC;IACnB;;;;;;;;OAQG;IACH,QAAQ,EAAE,eAAe,CAAC;CAC7B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,gBAAgB;IAC7B,uEAAuE;IACvE,SAAS,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAC5E;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChG"}