@superjackfruitlabs/salaam 0.0.1 → 0.0.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/CHANGELOG.md +15 -0
- package/README.md +25 -17
- package/dist/src/a2a-backend.js +225 -0
- package/dist/src/a2a-inbound.js +487 -0
- package/dist/src/a2a-peer.js +370 -0
- package/dist/src/a2a-protocol.js +250 -0
- package/dist/src/agent-profiles.js +289 -0
- package/dist/src/children.js +132 -26
- package/dist/src/claude-provider.js +2 -179
- package/dist/src/cli-help.js +107 -6
- package/dist/src/cli.js +477 -13
- package/dist/src/config.js +26 -3
- package/dist/src/credentials.js +7 -2
- package/dist/src/external-runtime.js +1 -0
- package/dist/src/external-work.js +583 -0
- package/dist/src/harness-backend.js +466 -0
- package/dist/src/harness-container.js +450 -0
- package/dist/src/harness-model-gateway.js +104 -0
- package/dist/src/harness-prepare.js +88 -0
- package/dist/src/harness-probe.js +111 -0
- package/dist/src/harness-protocol.js +325 -0
- package/dist/src/harness-readiness.js +50 -0
- package/dist/src/harness-rpc.js +206 -0
- package/dist/src/harness-tool-identity.js +87 -0
- package/dist/src/harness-wire-bridge.js +387 -0
- package/dist/src/host.js +6 -1
- package/dist/src/house-extension.js +147 -64
- package/dist/src/live-provider.js +301 -0
- package/dist/src/managed.js +33 -3
- package/dist/src/protocol.js +9 -5
- package/dist/src/provider-auth.js +300 -0
- package/dist/src/provider-config.js +65 -17
- package/dist/src/provider-model.js +78 -0
- package/dist/src/provider-payload.js +264 -0
- package/dist/src/provider-transport.js +36 -0
- package/dist/src/provider-usage.js +59 -0
- package/dist/src/repository-snapshots.js +219 -0
- package/dist/src/service.js +274 -9
- package/dist/src/store.js +44 -2
- package/dist/src/supervisor.js +246 -35
- package/dist/src/task-budget.js +17 -8
- package/dist/src/task-context.js +15 -0
- package/dist/src/task-driver.js +38 -14
- package/dist/src/task-policy.js +20 -1
- package/dist/src/task-verification.js +3 -0
- package/dist/src/task-work.js +31 -0
- package/dist/src/team-work.js +378 -0
- package/dist/src/tui-view.js +65 -10
- package/dist/src/tui.js +4 -2
- package/dist/src/usage.js +18 -0
- package/dist/src/worker-boundary.js +298 -0
- package/dist/src/worker-workspaces.js +274 -0
- package/dist/src/workspaces.js +6 -2
- package/harness-runtime/codex-pins.json +10 -0
- package/harness-runtime/package-lock.json +560 -0
- package/harness-runtime/package.json +9 -0
- package/npm-shrinkwrap.json +57 -2
- package/package.json +2 -1
- package/src/a2a-backend.ts +254 -0
- package/src/a2a-inbound.ts +585 -0
- package/src/a2a-peer.ts +441 -0
- package/src/a2a-protocol.ts +286 -0
- package/src/agent-profiles.ts +379 -0
- package/src/children.ts +168 -27
- package/src/claude-provider.ts +2 -189
- package/src/cli-help.ts +107 -6
- package/src/cli.ts +602 -12
- package/src/config.ts +34 -3
- package/src/credentials.ts +9 -2
- package/src/external-runtime.ts +54 -0
- package/src/external-work.ts +663 -0
- package/src/harness-backend.ts +557 -0
- package/src/harness-container.ts +543 -0
- package/src/harness-model-gateway.ts +138 -0
- package/src/harness-prepare.ts +113 -0
- package/src/harness-probe.ts +150 -0
- package/src/harness-protocol.ts +388 -0
- package/src/harness-readiness.ts +71 -0
- package/src/harness-rpc.ts +244 -0
- package/src/harness-tool-identity.ts +91 -0
- package/src/harness-wire-bridge.ts +418 -0
- package/src/host.ts +11 -6
- package/src/house-extension.ts +165 -66
- package/src/live-provider.ts +347 -0
- package/src/managed.ts +43 -4
- package/src/protocol.ts +36 -8
- package/src/provider-auth.ts +336 -0
- package/src/provider-config.ts +89 -18
- package/src/provider-model.ts +86 -0
- package/src/provider-payload.ts +263 -0
- package/src/provider-transport.ts +51 -0
- package/src/provider-usage.ts +64 -0
- package/src/repository-snapshots.ts +259 -0
- package/src/service.ts +446 -11
- package/src/store.ts +60 -4
- package/src/supervisor.ts +294 -41
- package/src/task-budget.ts +24 -11
- package/src/task-context.ts +17 -0
- package/src/task-driver.ts +51 -15
- package/src/task-policy.ts +23 -3
- package/src/task-verification.ts +2 -0
- package/src/task-work.ts +46 -1
- package/src/team-work.ts +554 -0
- package/src/tui-client.ts +5 -0
- package/src/tui-view.ts +164 -12
- package/src/tui.ts +5 -2
- package/src/usage.ts +17 -0
- package/src/worker-boundary.ts +395 -0
- package/src/worker-workspaces.ts +390 -0
- package/src/workspaces.ts +4 -2
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import {
|
|
3
|
+
readFileSync,
|
|
4
|
+
lstatSync,
|
|
5
|
+
realpathSync,
|
|
6
|
+
readdirSync,
|
|
7
|
+
readlinkSync,
|
|
8
|
+
createReadStream,
|
|
9
|
+
mkdirSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
existsSync,
|
|
12
|
+
} from 'node:fs';
|
|
13
|
+
import { join, relative, dirname, isAbsolute } from 'node:path';
|
|
14
|
+
import { DockerHarnessBoundary, type HarnessContainers } from './harness-container.js';
|
|
15
|
+
import { DockerCodingExecutor } from './coding-executor.js';
|
|
16
|
+
import { engineRequest, engineJson } from './execution-boundary.js';
|
|
17
|
+
import { HarnessProtocol, type HarnessCheckpoint } from './harness-protocol.js';
|
|
18
|
+
import { HARNESS_TARGETS, type HarnessTarget } from './harness-probe.js';
|
|
19
|
+
import { AnthropicHarnessGateway } from './harness-model-gateway.js';
|
|
20
|
+
import { HarnessWireBridge } from './harness-wire-bridge.js';
|
|
21
|
+
import { harnessToolIds, matchesCodexCommandScope } from './harness-tool-identity.js';
|
|
22
|
+
import { proveHarnessReadiness } from './harness-readiness.js';
|
|
23
|
+
import { ProviderCredentialStore, resolveProviderAuth } from './provider-auth.js';
|
|
24
|
+
import { providerRoute } from './provider-config.js';
|
|
25
|
+
import { readTree, validateTree, type WorkspaceTree } from './workspaces.js';
|
|
26
|
+
import { repositoryPath } from './repository-snapshots.js';
|
|
27
|
+
import { digest, canonical, ServiceError } from './protocol.js';
|
|
28
|
+
import type { ExternalBackend, ExternalRuntime, ExternalHooks } from './external-runtime.js';
|
|
29
|
+
import type { WorkerBinding } from './worker-boundary.js';
|
|
30
|
+
import { hostDigests } from './host.js';
|
|
31
|
+
|
|
32
|
+
export async function harnessRuntimeDigest(root: string): Promise<string> {
|
|
33
|
+
const entries: unknown[] = [];
|
|
34
|
+
async function walk(dir: string): Promise<void> {
|
|
35
|
+
for (const name of readdirSync(dir).sort()) {
|
|
36
|
+
const p = join(dir, name),
|
|
37
|
+
s = lstatSync(p);
|
|
38
|
+
if (s.isDirectory()) await walk(p);
|
|
39
|
+
else if (s.isSymbolicLink()) {
|
|
40
|
+
if (!realpathSync(p).startsWith(root + '/'))
|
|
41
|
+
throw Error('Runtime symlink escapes prepared artifact');
|
|
42
|
+
entries.push({ path: relative(root, p), link: readlinkSync(p) });
|
|
43
|
+
} else if (s.isFile()) {
|
|
44
|
+
const hash = createHash('sha256');
|
|
45
|
+
for await (const chunk of createReadStream(p)) hash.update(chunk);
|
|
46
|
+
entries.push({
|
|
47
|
+
path: relative(root, p),
|
|
48
|
+
hash: hash.digest('hex'),
|
|
49
|
+
executable: Boolean(s.mode & 0o111),
|
|
50
|
+
});
|
|
51
|
+
} else throw Error('Unsupported harness runtime artifact');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
await walk(root);
|
|
55
|
+
return digest(entries);
|
|
56
|
+
}
|
|
57
|
+
export function assertHarnessArtifactRoot(root: string): void {
|
|
58
|
+
const allowed = [
|
|
59
|
+
'node_modules',
|
|
60
|
+
'codex',
|
|
61
|
+
'package.json',
|
|
62
|
+
'package-lock.json',
|
|
63
|
+
'runtime-manifest.json',
|
|
64
|
+
];
|
|
65
|
+
if (
|
|
66
|
+
!isAbsolute(root) ||
|
|
67
|
+
realpathSync(root) !== root ||
|
|
68
|
+
readdirSync(root).some((name) => !allowed.includes(name))
|
|
69
|
+
)
|
|
70
|
+
throw Error(
|
|
71
|
+
'Select a dedicated prepared artifact root containing only pinned runtime packages; source, state and user configuration are excluded',
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
interface Pin {
|
|
75
|
+
id: string;
|
|
76
|
+
target: HarnessTarget;
|
|
77
|
+
version: string;
|
|
78
|
+
runtime_root: string;
|
|
79
|
+
runtime_digest: string;
|
|
80
|
+
model: string;
|
|
81
|
+
output: number;
|
|
82
|
+
requests: number;
|
|
83
|
+
}
|
|
84
|
+
/** Host-owned registry and fixed launch templates. No project-controlled commands, inherited env or real keys enter containers. */
|
|
85
|
+
export class DockerHarnessBackend implements ExternalBackend {
|
|
86
|
+
private pins: Pin[];
|
|
87
|
+
private fileDigest: string;
|
|
88
|
+
private boundary?: DockerHarnessBoundary;
|
|
89
|
+
private descriptors: ExternalRuntime[] = [];
|
|
90
|
+
private protocols = new Map<string, HarnessProtocol>();
|
|
91
|
+
private receipts = new Map<string, HarnessContainers>();
|
|
92
|
+
constructor(
|
|
93
|
+
readonly path: string,
|
|
94
|
+
readonly root: string,
|
|
95
|
+
readonly socket: string,
|
|
96
|
+
readonly executor: DockerCodingExecutor,
|
|
97
|
+
readonly docker = process.platform === 'darwin'
|
|
98
|
+
? '/opt/homebrew/bin/docker'
|
|
99
|
+
: '/usr/bin/docker',
|
|
100
|
+
) {
|
|
101
|
+
const stat = lstatSync(path),
|
|
102
|
+
bytes = readFileSync(path);
|
|
103
|
+
if (
|
|
104
|
+
!stat.isFile() ||
|
|
105
|
+
stat.isSymbolicLink() ||
|
|
106
|
+
stat.uid !== process.getuid?.() ||
|
|
107
|
+
(stat.mode & 0o077) !== 0 ||
|
|
108
|
+
bytes.length > 65536
|
|
109
|
+
)
|
|
110
|
+
throw Error('Harness config must be an owner-only regular file, at most 64 KiB');
|
|
111
|
+
this.fileDigest = digest(bytes.toString());
|
|
112
|
+
const value = JSON.parse(bytes.toString());
|
|
113
|
+
if (
|
|
114
|
+
value.schema !== 1 ||
|
|
115
|
+
Object.keys(value).some((k) => !['schema', 'runtimes'].includes(k)) ||
|
|
116
|
+
!Array.isArray(value.runtimes) ||
|
|
117
|
+
value.runtimes.length > 8
|
|
118
|
+
)
|
|
119
|
+
throw Error('Unsupported harness registry');
|
|
120
|
+
const ids = new Set<string>();
|
|
121
|
+
this.pins = value.runtimes.map((p: Pin) => {
|
|
122
|
+
if (
|
|
123
|
+
Object.keys(p).some(
|
|
124
|
+
(k) =>
|
|
125
|
+
![
|
|
126
|
+
'id',
|
|
127
|
+
'target',
|
|
128
|
+
'version',
|
|
129
|
+
'runtime_root',
|
|
130
|
+
'runtime_digest',
|
|
131
|
+
'model',
|
|
132
|
+
'output',
|
|
133
|
+
'requests',
|
|
134
|
+
].includes(k),
|
|
135
|
+
) ||
|
|
136
|
+
!/^[a-zA-Z0-9_-]{1,64}$/.test(p.id) ||
|
|
137
|
+
ids.has(p.id) ||
|
|
138
|
+
!Object.hasOwn(HARNESS_TARGETS, p.target) ||
|
|
139
|
+
p.version !== HARNESS_TARGETS[p.target].version ||
|
|
140
|
+
!isAbsolute(p.runtime_root) ||
|
|
141
|
+
realpathSync(p.runtime_root) !== p.runtime_root ||
|
|
142
|
+
!/^[a-f0-9]{64}$/.test(p.runtime_digest) ||
|
|
143
|
+
p.model !== 'claude-sonnet-4-6' ||
|
|
144
|
+
!Number.isSafeInteger(p.output) ||
|
|
145
|
+
p.output < 1 ||
|
|
146
|
+
p.output > 8192 ||
|
|
147
|
+
!Number.isSafeInteger(p.requests) ||
|
|
148
|
+
p.requests < 1 ||
|
|
149
|
+
p.requests > 32
|
|
150
|
+
)
|
|
151
|
+
throw Error('Select exact reviewed harness artifacts and the tested Sonnet 4.6 route');
|
|
152
|
+
ids.add(p.id);
|
|
153
|
+
assertHarnessArtifactRoot(p.runtime_root);
|
|
154
|
+
return structuredClone(p);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
async prepare(): Promise<void> {
|
|
158
|
+
if (this.executor.evidence.probe !== 'passed')
|
|
159
|
+
throw Error('Harness requires a proven real executor');
|
|
160
|
+
mkdirSync(this.root, { recursive: true, mode: 0o700 });
|
|
161
|
+
for (const p of this.pins) {
|
|
162
|
+
if ((await harnessRuntimeDigest(p.runtime_root)) !== p.runtime_digest)
|
|
163
|
+
throw Error('Harness runtime artifact changed');
|
|
164
|
+
if (p.target !== 'codex-app-server') {
|
|
165
|
+
const pkg = JSON.parse(
|
|
166
|
+
readFileSync(
|
|
167
|
+
join(
|
|
168
|
+
p.runtime_root,
|
|
169
|
+
'node_modules',
|
|
170
|
+
p.target === 'gemini-acp'
|
|
171
|
+
? '@google/gemini-cli/package.json'
|
|
172
|
+
: '@zed-industries/claude-agent-acp/package.json',
|
|
173
|
+
),
|
|
174
|
+
'utf8',
|
|
175
|
+
),
|
|
176
|
+
);
|
|
177
|
+
if (pkg.version !== p.version) throw Error('Harness package version differs from its pin');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const executable = realpathSync(this.docker);
|
|
181
|
+
this.boundary = new DockerHarnessBoundary(engineRequest(this.socket), this.executor.engineId, {
|
|
182
|
+
executable,
|
|
183
|
+
digest: createHash('sha256').update(readFileSync(executable)).digest('hex'),
|
|
184
|
+
socket: this.socket,
|
|
185
|
+
});
|
|
186
|
+
await proveHarnessReadiness(this.boundary, this.root);
|
|
187
|
+
this.descriptors = this.pins.map((p) => ({
|
|
188
|
+
id: p.id,
|
|
189
|
+
kind: 'harness',
|
|
190
|
+
target: p.target,
|
|
191
|
+
version: p.version,
|
|
192
|
+
config_digest: digest(p),
|
|
193
|
+
adapter_digest: hostDigests().build_digest,
|
|
194
|
+
execution_identity: {
|
|
195
|
+
version: String(this.executor.evidence.engine_version),
|
|
196
|
+
kernel: String(this.executor.evidence.kernel),
|
|
197
|
+
arch: String(this.executor.evidence.arch),
|
|
198
|
+
},
|
|
199
|
+
boundary_digest: this.boundary!.profileDigest,
|
|
200
|
+
engine_id: this.executor.engineId,
|
|
201
|
+
evidence: 'proven',
|
|
202
|
+
model_usage: 'unknown',
|
|
203
|
+
provider_route: {
|
|
204
|
+
provider: 'anthropic',
|
|
205
|
+
model: p.model,
|
|
206
|
+
wire:
|
|
207
|
+
p.target === 'claude-acp'
|
|
208
|
+
? 'anthropic'
|
|
209
|
+
: p.target === 'gemini-acp'
|
|
210
|
+
? 'google'
|
|
211
|
+
: 'responses',
|
|
212
|
+
wire_model: p.target === 'gemini-acp' ? 'gemini-2.5-flash' : p.model,
|
|
213
|
+
},
|
|
214
|
+
output_cap: p.output,
|
|
215
|
+
max_model_requests: p.requests,
|
|
216
|
+
files: true,
|
|
217
|
+
commands: true,
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
runtimes(): ExternalRuntime[] {
|
|
221
|
+
return structuredClone(this.descriptors);
|
|
222
|
+
}
|
|
223
|
+
validate(runtime: ExternalRuntime): void {
|
|
224
|
+
if (
|
|
225
|
+
!this.boundary ||
|
|
226
|
+
digest(readFileSync(this.path, 'utf8')) !== this.fileDigest ||
|
|
227
|
+
!this.descriptors.some((r) => digest(r) === digest(runtime))
|
|
228
|
+
)
|
|
229
|
+
throw new ServiceError(
|
|
230
|
+
'worker_binding_changed',
|
|
231
|
+
'Operator runtime configuration or proven boundary changed',
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
assertAuthority(runtime: ExternalRuntime, tools: readonly string[]): void {
|
|
235
|
+
if (runtime.target === 'codex-app-server' && !tools.includes('salaam_command'))
|
|
236
|
+
throw new ServiceError(
|
|
237
|
+
'runtime_role_unsupported',
|
|
238
|
+
'The pinned Codex route requires an explicitly command-enabled profile',
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
private runRoot(run: string): string {
|
|
242
|
+
if (!/^[0-9a-f-]{36}$/.test(run)) throw Error('Invalid owned run ID');
|
|
243
|
+
return join(this.root, run);
|
|
244
|
+
}
|
|
245
|
+
async execute(
|
|
246
|
+
binding: WorkerBinding,
|
|
247
|
+
run: string,
|
|
248
|
+
prompt: string,
|
|
249
|
+
tree: WorkspaceTree,
|
|
250
|
+
hooks: ExternalHooks,
|
|
251
|
+
): Promise<{ result: unknown }> {
|
|
252
|
+
this.validate(binding.external!);
|
|
253
|
+
const pin = this.pins.find((p) => p.id === binding.external!.id)!;
|
|
254
|
+
if ((await harnessRuntimeDigest(pin.runtime_root)) !== pin.runtime_digest)
|
|
255
|
+
throw Error('Harness runtime changed before launch');
|
|
256
|
+
hooks.assertActive();
|
|
257
|
+
const dir = this.runRoot(run),
|
|
258
|
+
work = join(dir, 'workspace'),
|
|
259
|
+
home = join(dir, 'home'),
|
|
260
|
+
token = randomUUID() + randomUUID();
|
|
261
|
+
if (existsSync(dir))
|
|
262
|
+
throw new ServiceError(
|
|
263
|
+
'harness_replay',
|
|
264
|
+
'Owned runtime directory already exists; reconcile without replay',
|
|
265
|
+
);
|
|
266
|
+
for (const p of [dir, work, home]) mkdirSync(p, { mode: 0o700 });
|
|
267
|
+
validateTree(tree);
|
|
268
|
+
for (const [p, file] of Object.entries(tree)) {
|
|
269
|
+
repositoryPath(p);
|
|
270
|
+
mkdirSync(dirname(join(work, p)), { recursive: true, mode: 0o700 });
|
|
271
|
+
writeFileSync(join(work, p), Buffer.from(file.content, 'base64'), {
|
|
272
|
+
mode: file.executable ? 0o700 : 0o600,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
const codex = pin.target === 'codex-app-server',
|
|
276
|
+
claude = pin.target === 'claude-acp';
|
|
277
|
+
const files = binding.tools.includes('salaam_files'),
|
|
278
|
+
commands = binding.tools.includes('salaam_command');
|
|
279
|
+
const tools = codex
|
|
280
|
+
? commands
|
|
281
|
+
? ['exec_command']
|
|
282
|
+
: []
|
|
283
|
+
: claude
|
|
284
|
+
? [
|
|
285
|
+
'Read',
|
|
286
|
+
'Glob',
|
|
287
|
+
'Grep',
|
|
288
|
+
...(files ? ['Edit', 'Write'] : []),
|
|
289
|
+
...(commands ? ['Bash'] : []),
|
|
290
|
+
]
|
|
291
|
+
: [
|
|
292
|
+
'list_directory',
|
|
293
|
+
'read_file',
|
|
294
|
+
'grep_search',
|
|
295
|
+
'glob',
|
|
296
|
+
...(files ? ['replace', 'write_file'] : []),
|
|
297
|
+
...(commands ? ['run_shell_command'] : []),
|
|
298
|
+
];
|
|
299
|
+
let args: string[], env: Record<string, string>;
|
|
300
|
+
if (codex) {
|
|
301
|
+
mkdirSync(join(home, '.codex'));
|
|
302
|
+
writeFileSync(
|
|
303
|
+
join(home, '.codex/config.toml'),
|
|
304
|
+
`model = "${pin.model}"\nmodel_provider = "salaam"\n[model_providers.salaam]\nname = "Salaam explicit Claude gateway"\nbase_url = "http://127.0.0.1:47123/v1"\nenv_key = "ANTHROPIC_API_KEY"\nwire_api = "responses"\nrequires_openai_auth = false\n`,
|
|
305
|
+
{ mode: 0o600 },
|
|
306
|
+
);
|
|
307
|
+
const arch = this.executor.evidence.arch === 'arm64' ? 'aarch64' : 'x86_64';
|
|
308
|
+
const binary = '/runtime/codex/vendor/' + arch + '-unknown-linux-musl/bin/codex';
|
|
309
|
+
const wrapper = `const cp=require('node:child_process');if(cp.execFileSync(${JSON.stringify(binary)},['--version'],{encoding:'utf8'}).trim()!=='codex-cli ${pin.version}')process.exit(2);const c=cp.spawn(${JSON.stringify(binary)},['app-server'],{stdio:'inherit',env:process.env});c.on('exit',n=>process.exit(n??1));c.on('error',()=>process.exit(2));`;
|
|
310
|
+
args = ['/usr/local/bin/node', '-e', wrapper];
|
|
311
|
+
env = { CODEX_HOME: '/home/agent/.codex', ANTHROPIC_API_KEY: token };
|
|
312
|
+
} else if (claude) {
|
|
313
|
+
args = [
|
|
314
|
+
'/usr/local/bin/node',
|
|
315
|
+
'/runtime/node_modules/@zed-industries/claude-agent-acp/dist/index.js',
|
|
316
|
+
];
|
|
317
|
+
env = {
|
|
318
|
+
ANTHROPIC_BASE_URL: 'http://127.0.0.1:47123',
|
|
319
|
+
ANTHROPIC_API_KEY: token,
|
|
320
|
+
ANTHROPIC_MODEL: pin.model,
|
|
321
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: pin.model,
|
|
322
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: pin.model,
|
|
323
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: pin.model,
|
|
324
|
+
DISABLE_TELEMETRY: '1',
|
|
325
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1',
|
|
326
|
+
};
|
|
327
|
+
} else {
|
|
328
|
+
args = [
|
|
329
|
+
'/usr/local/bin/node',
|
|
330
|
+
'/runtime/node_modules/@google/gemini-cli/bundle/gemini.js',
|
|
331
|
+
'--acp',
|
|
332
|
+
'--model',
|
|
333
|
+
'gemini-2.5-flash',
|
|
334
|
+
];
|
|
335
|
+
env = {
|
|
336
|
+
GEMINI_API_KEY: token,
|
|
337
|
+
GOOGLE_GEMINI_BASE_URL: 'http://127.0.0.1:47123',
|
|
338
|
+
GEMINI_CLI_NO_RELAUNCH: 'true',
|
|
339
|
+
DISABLE_TELEMETRY: '1',
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
const receipt = await this.boundary!.prepare(run, work, pin.runtime_root, home, args, env);
|
|
343
|
+
this.receipts.set(run, receipt);
|
|
344
|
+
hooks.receipt(receipt);
|
|
345
|
+
const intents = new Map<
|
|
346
|
+
string,
|
|
347
|
+
{ id: string; name: string; arguments: Record<string, unknown> }
|
|
348
|
+
>();
|
|
349
|
+
const checkBoundary = async () => {
|
|
350
|
+
try {
|
|
351
|
+
hooks.assertActive();
|
|
352
|
+
await this.boundary!.assert(receipt);
|
|
353
|
+
const info = engineJson(await engineRequest(this.socket)('GET', '/info'));
|
|
354
|
+
const identity = binding.external!.execution_identity;
|
|
355
|
+
if (info.ServerVersion !== identity.version || info.KernelVersion !== identity.kernel)
|
|
356
|
+
throw Error('Harness engine version or kernel changed');
|
|
357
|
+
} catch (error) {
|
|
358
|
+
try {
|
|
359
|
+
hooks.failed(error as Error);
|
|
360
|
+
} catch {}
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
const gateway = new AnthropicHarnessGateway({
|
|
365
|
+
model: pin.model,
|
|
366
|
+
output: pin.output,
|
|
367
|
+
token,
|
|
368
|
+
credential: async () => {
|
|
369
|
+
const auth = await resolveProviderAuth(
|
|
370
|
+
providerRoute('anthropic', pin.model, pin.output),
|
|
371
|
+
new ProviderCredentialStore(),
|
|
372
|
+
{},
|
|
373
|
+
);
|
|
374
|
+
if (!auth.auth.apiKey) throw Error('No operator-configured Claude credential');
|
|
375
|
+
return auth.auth.apiKey;
|
|
376
|
+
},
|
|
377
|
+
admit: hooks.modelRequest,
|
|
378
|
+
});
|
|
379
|
+
const bridge = claude
|
|
380
|
+
? null
|
|
381
|
+
: new HarnessWireBridge(
|
|
382
|
+
gateway,
|
|
383
|
+
{
|
|
384
|
+
protocol: codex ? 'responses' : 'google',
|
|
385
|
+
model: codex ? pin.model : 'gemini-2.5-flash',
|
|
386
|
+
tools,
|
|
387
|
+
},
|
|
388
|
+
(intent) => {
|
|
389
|
+
for (const id of harnessToolIds(pin.target, pin.version, intent))
|
|
390
|
+
intents.set(id, intent);
|
|
391
|
+
},
|
|
392
|
+
);
|
|
393
|
+
await this.boundary!.gateway(receipt, async (request) => {
|
|
394
|
+
await checkBoundary();
|
|
395
|
+
if (bridge) return bridge.request(request);
|
|
396
|
+
const body = JSON.parse(request.body.toString());
|
|
397
|
+
if (Array.isArray(body.tools))
|
|
398
|
+
body.tools = body.tools.filter((t: any) => tools.includes(t.name));
|
|
399
|
+
return gateway.request({ ...request, body: Buffer.from(JSON.stringify(body)) });
|
|
400
|
+
});
|
|
401
|
+
const checkpoint: HarnessCheckpoint = {
|
|
402
|
+
schema_version: 1,
|
|
403
|
+
dialect: codex ? 'codex' : 'acp',
|
|
404
|
+
worker_id: binding.worker_id,
|
|
405
|
+
attempt_id: binding.attempt_id,
|
|
406
|
+
state: 'prepared',
|
|
407
|
+
session_id: null,
|
|
408
|
+
turn_id: null,
|
|
409
|
+
prompt_digest: null,
|
|
410
|
+
cancel_requested: false,
|
|
411
|
+
result: null,
|
|
412
|
+
load_session: false,
|
|
413
|
+
};
|
|
414
|
+
hooks.checkpoint(checkpoint);
|
|
415
|
+
const protocol = new HarnessProtocol(
|
|
416
|
+
await this.boundary!.launchHarness(receipt, checkpoint.dialect),
|
|
417
|
+
checkpoint,
|
|
418
|
+
{
|
|
419
|
+
save: hooks.checkpoint,
|
|
420
|
+
permission: async (owned) => {
|
|
421
|
+
await checkBoundary();
|
|
422
|
+
const params = owned.params as any,
|
|
423
|
+
call = params.toolCall,
|
|
424
|
+
known = intents.get(codex ? params.itemId : call?.toolCallId);
|
|
425
|
+
let scope:
|
|
426
|
+
| { id: string; kind: 'write' | 'command'; arguments: Record<string, unknown> }
|
|
427
|
+
| undefined;
|
|
428
|
+
if (codex) {
|
|
429
|
+
if (matchesCodexCommandScope(pin.version, known, params))
|
|
430
|
+
scope = {
|
|
431
|
+
id: params.itemId,
|
|
432
|
+
kind: 'command',
|
|
433
|
+
arguments: { cmd: known!.arguments.cmd, cwd: params.cwd },
|
|
434
|
+
};
|
|
435
|
+
} else {
|
|
436
|
+
const input = (claude ? call?.rawInput : known?.arguments) as
|
|
437
|
+
Record<string, unknown> | undefined;
|
|
438
|
+
const name = claude ? call?.title : known?.name;
|
|
439
|
+
if (input && typeof input.command === 'string')
|
|
440
|
+
scope = { id: call.toolCallId, kind: 'command', arguments: input };
|
|
441
|
+
else if (
|
|
442
|
+
input &&
|
|
443
|
+
typeof input.file_path === 'string' &&
|
|
444
|
+
(claude || ['replace', 'write_file'].includes(String(name)))
|
|
445
|
+
) {
|
|
446
|
+
if (input.file_path.startsWith('/workspace/'))
|
|
447
|
+
repositoryPath(input.file_path.slice('/workspace/'.length));
|
|
448
|
+
else return 'deny';
|
|
449
|
+
scope = { id: call.toolCallId, kind: 'write', arguments: input };
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (!scope) return 'deny';
|
|
453
|
+
const decision = await hooks.permission(owned.method, {
|
|
454
|
+
scope,
|
|
455
|
+
upstream_session: owned.session_id,
|
|
456
|
+
upstream_turn: owned.turn_id,
|
|
457
|
+
request_digest: digest(params),
|
|
458
|
+
});
|
|
459
|
+
await checkBoundary();
|
|
460
|
+
return decision;
|
|
461
|
+
},
|
|
462
|
+
event: (method, params) => {
|
|
463
|
+
const p = params as any;
|
|
464
|
+
if (
|
|
465
|
+
!codex &&
|
|
466
|
+
method === 'session/update' &&
|
|
467
|
+
p.update?.sessionUpdate === 'tool_call_update' &&
|
|
468
|
+
['completed', 'failed'].includes(p.update.status)
|
|
469
|
+
)
|
|
470
|
+
hooks.toolResult(p.update.toolCallId, p.update.status === 'completed', p.update);
|
|
471
|
+
if (
|
|
472
|
+
codex &&
|
|
473
|
+
method === 'item/completed' &&
|
|
474
|
+
['commandExecution', 'fileChange'].includes(p.item?.type)
|
|
475
|
+
)
|
|
476
|
+
hooks.toolResult(
|
|
477
|
+
p.item.id,
|
|
478
|
+
p.item.status === 'completed' &&
|
|
479
|
+
(p.item.exitCode === undefined || p.item.exitCode === 0),
|
|
480
|
+
p.item,
|
|
481
|
+
);
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
);
|
|
485
|
+
this.protocols.set(run, protocol);
|
|
486
|
+
await protocol.initialize(!codex);
|
|
487
|
+
if (!codex)
|
|
488
|
+
await protocol.rpc.request(
|
|
489
|
+
'authenticate',
|
|
490
|
+
{
|
|
491
|
+
methodId: 'gateway',
|
|
492
|
+
_meta: {
|
|
493
|
+
...(claude ? {} : { 'api-key': token }),
|
|
494
|
+
gateway: { baseUrl: 'http://127.0.0.1:47123', headers: { 'x-api-key': token } },
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
30000,
|
|
498
|
+
);
|
|
499
|
+
await protocol.create(
|
|
500
|
+
codex
|
|
501
|
+
? {
|
|
502
|
+
cwd: '/workspace',
|
|
503
|
+
model: pin.model,
|
|
504
|
+
modelProvider: 'salaam',
|
|
505
|
+
sandbox: 'danger-full-access',
|
|
506
|
+
approvalPolicy: 'untrusted',
|
|
507
|
+
}
|
|
508
|
+
: {
|
|
509
|
+
cwd: '/workspace',
|
|
510
|
+
mcpServers: [],
|
|
511
|
+
...(claude
|
|
512
|
+
? {
|
|
513
|
+
_meta: {
|
|
514
|
+
claudeCode: {
|
|
515
|
+
options: {
|
|
516
|
+
model: pin.model,
|
|
517
|
+
settingSources: [],
|
|
518
|
+
plugins: [],
|
|
519
|
+
tools,
|
|
520
|
+
disallowedTools: [
|
|
521
|
+
'Skill',
|
|
522
|
+
'Task',
|
|
523
|
+
'Agent',
|
|
524
|
+
'WebFetch',
|
|
525
|
+
'WebSearch',
|
|
526
|
+
'AskUserQuestion',
|
|
527
|
+
],
|
|
528
|
+
maxTurns: pin.requests,
|
|
529
|
+
maxThinkingTokens: 1024,
|
|
530
|
+
},
|
|
531
|
+
},
|
|
532
|
+
},
|
|
533
|
+
}
|
|
534
|
+
: {}),
|
|
535
|
+
},
|
|
536
|
+
);
|
|
537
|
+
return { result: await protocol.prompt(prompt) };
|
|
538
|
+
}
|
|
539
|
+
async stop(run: string, runtime: ExternalRuntime, _receipt: unknown): Promise<void> {
|
|
540
|
+
if (
|
|
541
|
+
!this.boundary ||
|
|
542
|
+
runtime.engine_id !== this.executor.engineId ||
|
|
543
|
+
runtime.boundary_digest !== this.boundary.profileDigest
|
|
544
|
+
)
|
|
545
|
+
throw Error('Cannot establish the original harness engine/profile for cleanup');
|
|
546
|
+
try {
|
|
547
|
+
await this.protocols.get(run)?.close();
|
|
548
|
+
} finally {
|
|
549
|
+
this.protocols.delete(run);
|
|
550
|
+
await this.boundary.cleanupRun(run);
|
|
551
|
+
this.receipts.delete(run);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
collect(run: string, _runtime: ExternalRuntime): WorkspaceTree {
|
|
555
|
+
return readTree(join(this.runRoot(run), 'workspace'));
|
|
556
|
+
}
|
|
557
|
+
}
|