@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.
Files changed (110) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +25 -17
  3. package/dist/src/a2a-backend.js +225 -0
  4. package/dist/src/a2a-inbound.js +487 -0
  5. package/dist/src/a2a-peer.js +370 -0
  6. package/dist/src/a2a-protocol.js +250 -0
  7. package/dist/src/agent-profiles.js +289 -0
  8. package/dist/src/children.js +132 -26
  9. package/dist/src/claude-provider.js +2 -179
  10. package/dist/src/cli-help.js +107 -6
  11. package/dist/src/cli.js +477 -13
  12. package/dist/src/config.js +26 -3
  13. package/dist/src/credentials.js +7 -2
  14. package/dist/src/external-runtime.js +1 -0
  15. package/dist/src/external-work.js +583 -0
  16. package/dist/src/harness-backend.js +466 -0
  17. package/dist/src/harness-container.js +450 -0
  18. package/dist/src/harness-model-gateway.js +104 -0
  19. package/dist/src/harness-prepare.js +88 -0
  20. package/dist/src/harness-probe.js +111 -0
  21. package/dist/src/harness-protocol.js +325 -0
  22. package/dist/src/harness-readiness.js +50 -0
  23. package/dist/src/harness-rpc.js +206 -0
  24. package/dist/src/harness-tool-identity.js +87 -0
  25. package/dist/src/harness-wire-bridge.js +387 -0
  26. package/dist/src/host.js +6 -1
  27. package/dist/src/house-extension.js +147 -64
  28. package/dist/src/live-provider.js +301 -0
  29. package/dist/src/managed.js +33 -3
  30. package/dist/src/protocol.js +9 -5
  31. package/dist/src/provider-auth.js +300 -0
  32. package/dist/src/provider-config.js +65 -17
  33. package/dist/src/provider-model.js +78 -0
  34. package/dist/src/provider-payload.js +264 -0
  35. package/dist/src/provider-transport.js +36 -0
  36. package/dist/src/provider-usage.js +59 -0
  37. package/dist/src/repository-snapshots.js +219 -0
  38. package/dist/src/service.js +274 -9
  39. package/dist/src/store.js +44 -2
  40. package/dist/src/supervisor.js +246 -35
  41. package/dist/src/task-budget.js +17 -8
  42. package/dist/src/task-context.js +15 -0
  43. package/dist/src/task-driver.js +38 -14
  44. package/dist/src/task-policy.js +20 -1
  45. package/dist/src/task-verification.js +3 -0
  46. package/dist/src/task-work.js +31 -0
  47. package/dist/src/team-work.js +378 -0
  48. package/dist/src/tui-view.js +65 -10
  49. package/dist/src/tui.js +4 -2
  50. package/dist/src/usage.js +18 -0
  51. package/dist/src/worker-boundary.js +298 -0
  52. package/dist/src/worker-workspaces.js +274 -0
  53. package/dist/src/workspaces.js +6 -2
  54. package/harness-runtime/codex-pins.json +10 -0
  55. package/harness-runtime/package-lock.json +560 -0
  56. package/harness-runtime/package.json +9 -0
  57. package/npm-shrinkwrap.json +57 -2
  58. package/package.json +2 -1
  59. package/src/a2a-backend.ts +254 -0
  60. package/src/a2a-inbound.ts +585 -0
  61. package/src/a2a-peer.ts +441 -0
  62. package/src/a2a-protocol.ts +286 -0
  63. package/src/agent-profiles.ts +379 -0
  64. package/src/children.ts +168 -27
  65. package/src/claude-provider.ts +2 -189
  66. package/src/cli-help.ts +107 -6
  67. package/src/cli.ts +602 -12
  68. package/src/config.ts +34 -3
  69. package/src/credentials.ts +9 -2
  70. package/src/external-runtime.ts +54 -0
  71. package/src/external-work.ts +663 -0
  72. package/src/harness-backend.ts +557 -0
  73. package/src/harness-container.ts +543 -0
  74. package/src/harness-model-gateway.ts +138 -0
  75. package/src/harness-prepare.ts +113 -0
  76. package/src/harness-probe.ts +150 -0
  77. package/src/harness-protocol.ts +388 -0
  78. package/src/harness-readiness.ts +71 -0
  79. package/src/harness-rpc.ts +244 -0
  80. package/src/harness-tool-identity.ts +91 -0
  81. package/src/harness-wire-bridge.ts +418 -0
  82. package/src/host.ts +11 -6
  83. package/src/house-extension.ts +165 -66
  84. package/src/live-provider.ts +347 -0
  85. package/src/managed.ts +43 -4
  86. package/src/protocol.ts +36 -8
  87. package/src/provider-auth.ts +336 -0
  88. package/src/provider-config.ts +89 -18
  89. package/src/provider-model.ts +86 -0
  90. package/src/provider-payload.ts +263 -0
  91. package/src/provider-transport.ts +51 -0
  92. package/src/provider-usage.ts +64 -0
  93. package/src/repository-snapshots.ts +259 -0
  94. package/src/service.ts +446 -11
  95. package/src/store.ts +60 -4
  96. package/src/supervisor.ts +294 -41
  97. package/src/task-budget.ts +24 -11
  98. package/src/task-context.ts +17 -0
  99. package/src/task-driver.ts +51 -15
  100. package/src/task-policy.ts +23 -3
  101. package/src/task-verification.ts +2 -0
  102. package/src/task-work.ts +46 -1
  103. package/src/team-work.ts +554 -0
  104. package/src/tui-client.ts +5 -0
  105. package/src/tui-view.ts +164 -12
  106. package/src/tui.ts +5 -2
  107. package/src/usage.ts +17 -0
  108. package/src/worker-boundary.ts +395 -0
  109. package/src/worker-workspaces.ts +390 -0
  110. package/src/workspaces.ts +4 -2
@@ -0,0 +1,450 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
3
+ import { realpathSync, lstatSync, readFileSync } from 'node:fs';
4
+ import { isAbsolute } from 'node:path';
5
+ import { engineJson } from './execution-boundary.js';
6
+ import { digest, ServiceError } from './protocol.js';
7
+ const OWNER = 'salaam.harness.run', PROFILE = 'salaam.harness.profile';
8
+ // Gemini's pinned Linux shell tool invokes Bash. Prepare this image outside model turns.
9
+ export const HARNESS_IMAGE = 'docker.io/library/node@sha256:4db36457f406501e6f608802e5da617e5fbd0e80b75901b6a09de1ae5a667d32';
10
+ export const HARNESS_CONTAINER_PROFILE = {
11
+ version: 2,
12
+ image: HARNESS_IMAGE,
13
+ user: 'daemon-owner-nonroot',
14
+ network: 'gateway-loopback-only',
15
+ read_only: true,
16
+ capabilities: [],
17
+ no_new_privileges: true,
18
+ pids: 96,
19
+ memory: 768 * 1024 * 1024,
20
+ tmp_bytes: 64 * 1024 * 1024,
21
+ deadline_ms: 300000,
22
+ request_bytes: 2 * 1024 * 1024,
23
+ response_bytes: 8 * 1024 * 1024,
24
+ };
25
+ /** Fixed relay: neither this container nor the harness has an external network. Real keys stay in the host callback. */
26
+ const GATEWAY = String.raw `const http=require('node:http'),rl=require('node:readline');let serial=0;const pending=new Map();const server=http.createServer(async(req,res)=>{if(pending.size>=8){res.writeHead(429);res.end();return}let size=0,chunks=[];try{for await(const chunk of req){size+=chunk.length;if(size>2097152)throw Error('body limit');chunks.push(chunk)}const id=++serial;pending.set(id,res);res.on('close',()=>pending.delete(id));const frame=JSON.stringify({id,method:req.method,path:req.url,headers:{'content-type':req.headers['content-type'],'x-api-key':req.headers['x-api-key'],authorization:req.headers.authorization,'anthropic-beta':req.headers['anthropic-beta']},body:Buffer.concat(chunks).toString('base64')})+'\n';if(process.stdout.writableLength+frame.length>4194304)throw Error('relay backpressure');process.stdout.write(frame);}catch{res.destroy()}});rl.createInterface({input:process.stdin}).on('line',line=>{try{if(line.length>4194304)throw Error('frame limit');const m=JSON.parse(line),res=pending.get(m.id);if(!res)return;if(m.status!==undefined)res.writeHead(m.status,{'content-type':m.content_type??'application/json'});if(m.chunk)res.write(Buffer.from(m.chunk,'base64'));if(m.end){res.end();pending.delete(m.id)}}catch{process.exit(2)}}).on('close',()=>{for(const res of pending.values())res.destroy();server.close();process.exit(0)});server.listen(47123,'127.0.0.1',()=>process.stdout.write(JSON.stringify({ready:true})+'\n'));setTimeout(()=>process.exit(3),300000);`;
27
+ // PID 1 owns the wall clock and exits with the harness, killing detached descendants.
28
+ const BOOT = `const {spawn}=require('node:child_process');const child=spawn(process.argv[1],process.argv.slice(2),{stdio:'inherit',env:process.env});setTimeout(()=>process.exit(124),300000);child.on('error',()=>process.exit(126));child.on('exit',code=>process.exit(code??125));`;
29
+ export class DockerHarnessBoundary {
30
+ engine;
31
+ engineId;
32
+ docker;
33
+ user;
34
+ profileDigest;
35
+ imageEnv = [];
36
+ active = new Map();
37
+ constructor(engine, engineId, docker) {
38
+ this.engine = engine;
39
+ this.engineId = engineId;
40
+ this.docker = docker;
41
+ const uid = process.getuid?.(), gid = process.getgid?.();
42
+ if (!uid || gid === undefined || gid === 0)
43
+ throw new ServiceError('harness_user_unavailable', 'Require a non-root daemon owner');
44
+ this.user = uid + ':' + gid;
45
+ this.profileDigest = digest({
46
+ profile: HARNESS_CONTAINER_PROFILE,
47
+ user: this.user,
48
+ gateway: GATEWAY,
49
+ boot: BOOT,
50
+ });
51
+ }
52
+ configuration(c) {
53
+ const config = c.Config ?? c, host = c.HostConfig;
54
+ const fields = [
55
+ 'NetworkMode',
56
+ 'ReadonlyRootfs',
57
+ 'CapDrop',
58
+ 'CapAdd',
59
+ 'SecurityOpt',
60
+ 'Privileged',
61
+ 'PidMode',
62
+ 'IpcMode',
63
+ 'CgroupnsMode',
64
+ 'PidsLimit',
65
+ 'Memory',
66
+ 'MemorySwap',
67
+ 'NanoCpus',
68
+ 'ShmSize',
69
+ 'Tmpfs',
70
+ 'Ulimits',
71
+ 'RestartPolicy',
72
+ ];
73
+ const effective = Object.fromEntries(fields.map((k) => [k, host[k] ?? null]));
74
+ effective.CapAdd = host.CapAdd ?? [];
75
+ effective.SecurityOpt = (host.SecurityOpt ?? [])
76
+ .map((v) => v.replace(':true', ''))
77
+ .sort();
78
+ effective.RestartPolicy = { Name: host.RestartPolicy?.Name };
79
+ const mounts = c.Mounts
80
+ ? c.Mounts.map((m) => ({
81
+ source: m.Source,
82
+ target: m.Destination,
83
+ read_only: !m.RW,
84
+ type: m.Type,
85
+ propagation: m.Propagation,
86
+ }))
87
+ : host.Mounts.map((m) => ({
88
+ source: m.Source,
89
+ target: m.Target,
90
+ read_only: m.ReadOnly,
91
+ type: m.Type,
92
+ propagation: m.BindOptions.Propagation,
93
+ }));
94
+ return digest({
95
+ image: config.Image,
96
+ user: config.User,
97
+ env: [...config.Env].sort(),
98
+ entrypoint: config.Entrypoint ?? [],
99
+ args: config.Cmd,
100
+ cwd: config.WorkingDir,
101
+ tty: config.Tty,
102
+ stdin: config.OpenStdin,
103
+ stdin_once: config.StdinOnce,
104
+ labels: config.Labels,
105
+ host: effective,
106
+ mounts: mounts.sort((a, b) => a.target.localeCompare(b.target)),
107
+ logs: host.LogConfig?.Type,
108
+ });
109
+ }
110
+ mount(source, target, readOnly) {
111
+ if (!isAbsolute(source) ||
112
+ /[\0\r\n,]/.test(source) ||
113
+ realpathSync(source) !== source ||
114
+ !lstatSync(source).isDirectory())
115
+ throw new ServiceError('harness_mount_denied', 'Require a canonical prepared directory');
116
+ return {
117
+ Type: 'bind',
118
+ Source: source,
119
+ Target: target,
120
+ ReadOnly: readOnly,
121
+ BindOptions: { Propagation: 'rprivate' },
122
+ };
123
+ }
124
+ config(run, gateway, mounts, args, env, network) {
125
+ return {
126
+ Image: HARNESS_IMAGE,
127
+ User: this.user,
128
+ Env: [
129
+ ...new Map([
130
+ ...this.imageEnv,
131
+ 'HOME=/home/agent',
132
+ 'TMPDIR=/tmp',
133
+ 'PATH=/usr/local/bin:/usr/bin:/bin',
134
+ ...env,
135
+ ].map((v) => [v.slice(0, v.indexOf('=')), v])).values(),
136
+ ],
137
+ Entrypoint: ['/usr/local/bin/node'],
138
+ Cmd: gateway ? args : ['-e', BOOT, '--', ...args],
139
+ WorkingDir: gateway ? '/tmp' : '/workspace',
140
+ Hostname: gateway ? 'salaam-harness' : '',
141
+ Tty: false,
142
+ OpenStdin: true,
143
+ StdinOnce: true,
144
+ AttachStdin: true,
145
+ AttachStdout: true,
146
+ AttachStderr: true,
147
+ Labels: {
148
+ [OWNER]: run,
149
+ [PROFILE]: this.profileDigest,
150
+ 'salaam.harness.kind': gateway ? 'gateway' : 'worker',
151
+ },
152
+ HostConfig: {
153
+ NetworkMode: network,
154
+ ReadonlyRootfs: true,
155
+ CapDrop: ['ALL'],
156
+ SecurityOpt: ['no-new-privileges:true'],
157
+ Privileged: false,
158
+ PidMode: '',
159
+ IpcMode: 'private',
160
+ CgroupnsMode: 'private',
161
+ PidsLimit: HARNESS_CONTAINER_PROFILE.pids,
162
+ Memory: gateway ? 128 * 1024 * 1024 : HARNESS_CONTAINER_PROFILE.memory,
163
+ MemorySwap: gateway ? 128 * 1024 * 1024 : HARNESS_CONTAINER_PROFILE.memory,
164
+ NanoCpus: 1000000000,
165
+ ShmSize: 8 * 1024 * 1024,
166
+ Mounts: mounts,
167
+ Tmpfs: {
168
+ '/tmp': `rw,nosuid,nodev,size=64m,uid=${this.user.split(':')[0]},gid=${this.user.split(':')[1]},mode=700`,
169
+ },
170
+ Ulimits: [
171
+ { Name: 'nofile', Soft: 256, Hard: 256 },
172
+ { Name: 'fsize', Soft: 16 * 1024 * 1024, Hard: 16 * 1024 * 1024 },
173
+ ],
174
+ RestartPolicy: { Name: 'no' },
175
+ LogConfig: { Type: 'none' },
176
+ },
177
+ };
178
+ }
179
+ async prepare(run, workspace, runtime, home, args, env = {}) {
180
+ if (!/^[0-9a-f-]{36}$/.test(run) ||
181
+ !args.length ||
182
+ args.some((a) => typeof a !== 'string' || a.length > 8192) ||
183
+ Object.keys(env).some((k) => ![
184
+ 'ANTHROPIC_BASE_URL',
185
+ 'ANTHROPIC_API_KEY',
186
+ 'ANTHROPIC_AUTH_TOKEN',
187
+ 'ANTHROPIC_MODEL',
188
+ 'ANTHROPIC_DEFAULT_SONNET_MODEL',
189
+ 'ANTHROPIC_DEFAULT_HAIKU_MODEL',
190
+ 'ANTHROPIC_DEFAULT_OPUS_MODEL',
191
+ 'GEMINI_API_KEY',
192
+ 'GOOGLE_GEMINI_BASE_URL',
193
+ 'GEMINI_CLI_NO_RELAUNCH',
194
+ 'CODEX_HOME',
195
+ 'DISABLE_TELEMETRY',
196
+ 'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC',
197
+ 'CLAUDE_CONFIG_DIR',
198
+ ].includes(k)))
199
+ throw new ServiceError('harness_launch_denied', 'Unsupported prepared harness launch');
200
+ const info = engineJson(await this.engine('GET', '/info'));
201
+ if (info.ID !== this.engineId ||
202
+ info.CgroupVersion !== '2' ||
203
+ !info.SecurityOptions?.includes('name=seccomp,profile=builtin'))
204
+ throw new ServiceError('harness_engine_changed', 'Proven engine changed');
205
+ const image = engineJson(await this.engine('GET', `/images/${encodeURIComponent(HARNESS_IMAGE)}/json`));
206
+ if (!image.RepoDigests?.some((r) => [
207
+ HARNESS_IMAGE,
208
+ HARNESS_IMAGE.replace('docker.io/', ''),
209
+ HARNESS_IMAGE.replace('docker.io/library/', ''),
210
+ ].includes(r)) ||
211
+ !/^sha256:[a-f0-9]{64}$/.test(image.Id))
212
+ throw new ServiceError('harness_image_changed', 'Pinned harness image is unavailable');
213
+ this.imageEnv = image.Config.Env;
214
+ const gatewayConfig = this.config(run, true, [], ['-e', GATEWAY], [], 'none');
215
+ const gateway = String(engineJson(await this.engine('POST', '/containers/create?name=salaam-harness-' + run + '-gateway', gatewayConfig), [201]).Id);
216
+ let harness;
217
+ try {
218
+ const config = this.config(run, false, [
219
+ this.mount(workspace, '/workspace', false),
220
+ this.mount(runtime, '/runtime', true),
221
+ this.mount(home, '/home/agent', false),
222
+ ], args, Object.entries(env).map(([k, v]) => k + '=' + v), 'container:' + gateway);
223
+ harness = String(engineJson(await this.engine('POST', '/containers/create?name=salaam-harness-' + run + '-worker', config), [201]).Id);
224
+ const receipt = {
225
+ run_id: run,
226
+ gateway,
227
+ harness,
228
+ profile_digest: this.profileDigest,
229
+ engine_id: this.engineId,
230
+ image_id: image.Id,
231
+ gateway_config: this.configuration(gatewayConfig),
232
+ harness_config: this.configuration(config),
233
+ };
234
+ await this.assert(receipt);
235
+ return receipt;
236
+ }
237
+ catch (error) {
238
+ if (harness)
239
+ await this.engine('DELETE', `/containers/${harness}?force=true`);
240
+ await this.engine('DELETE', `/containers/${gateway}?force=true`);
241
+ throw error;
242
+ }
243
+ }
244
+ async owned(id, run, kind) {
245
+ if (!/^[a-f0-9]{64}$/.test(id))
246
+ throw new ServiceError('harness_container_changed', 'Invalid container identity');
247
+ const c = engineJson(await this.engine('GET', `/containers/${id}/json`));
248
+ const h = c.HostConfig;
249
+ if (c.Config.Labels?.[OWNER] !== run ||
250
+ c.Config.Labels?.[PROFILE] !== this.profileDigest ||
251
+ c.Config.Labels?.['salaam.harness.kind'] !== kind ||
252
+ c.Config.User !== this.user ||
253
+ h.Privileged ||
254
+ !h.ReadonlyRootfs ||
255
+ digest(h.CapDrop) !== digest(['ALL']) ||
256
+ (!h.SecurityOpt?.includes('no-new-privileges') &&
257
+ !h.SecurityOpt?.includes('no-new-privileges:true')) ||
258
+ h.PidsLimit !== HARNESS_CONTAINER_PROFILE.pids ||
259
+ h.RestartPolicy?.Name !== 'no')
260
+ throw new ServiceError('harness_container_changed', 'Owned harness configuration changed');
261
+ return c;
262
+ }
263
+ async assert(receipt) {
264
+ const info = engineJson(await this.engine('GET', '/info'));
265
+ if (info.ID !== this.engineId ||
266
+ info.CgroupVersion !== '2' ||
267
+ !info.SecurityOptions?.includes('name=seccomp,profile=builtin'))
268
+ throw new ServiceError('harness_engine_changed', 'Proven engine changed before dispatch');
269
+ if (receipt.engine_id !== this.engineId || receipt.profile_digest !== this.profileDigest)
270
+ throw new ServiceError('harness_container_changed', 'Boundary receipt changed');
271
+ const [g, h] = await Promise.all([
272
+ this.owned(receipt.gateway, receipt.run_id, 'gateway'),
273
+ this.owned(receipt.harness, receipt.run_id, 'worker'),
274
+ ]);
275
+ if (g.Image !== receipt.image_id ||
276
+ h.Image !== receipt.image_id ||
277
+ this.configuration(g) !== receipt.gateway_config ||
278
+ this.configuration(h) !== receipt.harness_config ||
279
+ g.HostConfig.NetworkMode !== 'none' ||
280
+ h.HostConfig.NetworkMode !== 'container:' + receipt.gateway ||
281
+ g.Mounts.length !== 0 ||
282
+ h.Mounts.length !== 3 ||
283
+ h.Mounts.some((m) => !['/workspace', '/runtime', '/home/agent'].includes(m.Destination) ||
284
+ m.Type !== 'bind' ||
285
+ (m.Destination === '/runtime' && m.RW)))
286
+ throw new ServiceError('harness_container_changed', 'Harness network or mounts changed');
287
+ }
288
+ launch(id, dialect) {
289
+ if (createHash('sha256').update(readFileSync(this.docker.executable)).digest('hex') !==
290
+ this.docker.digest)
291
+ throw new ServiceError('harness_docker_changed', 'Operator Docker executable changed');
292
+ return {
293
+ executable: this.docker.executable,
294
+ args: ['--host', 'unix://' + this.docker.socket, 'start', '--attach', '--interactive', id],
295
+ cwd: '/',
296
+ env: { HOME: '/nonexistent', PATH: '/usr/bin:/bin' },
297
+ dialect,
298
+ };
299
+ }
300
+ async gateway(receipt, handler) {
301
+ await this.assert(receipt);
302
+ const launch = this.launch(receipt.gateway, 'acp'), process = spawn(launch.executable, launch.args, {
303
+ cwd: launch.cwd,
304
+ env: launch.env,
305
+ stdio: ['pipe', 'pipe', 'pipe'],
306
+ });
307
+ this.active.set(receipt.run_id, process);
308
+ let buffer = '', total = 0, ready = false, inflight = 0;
309
+ const ids = new Set();
310
+ const send = (message) => {
311
+ const frame = JSON.stringify(message) + '\n';
312
+ if (process.stdin.writableLength + frame.length > 4 * 1024 * 1024)
313
+ throw Error('Gateway backpressure');
314
+ process.stdin.write(frame);
315
+ };
316
+ const started = new Promise((resolve, reject) => {
317
+ const timer = setTimeout(() => {
318
+ reject(Error('Gateway start deadline'));
319
+ process.kill('SIGKILL');
320
+ }, 10000);
321
+ process.on('error', reject);
322
+ process.stdin.on('error', () => process.kill('SIGKILL'));
323
+ process.stderr.on('data', (bytes) => {
324
+ total += bytes.length;
325
+ if (total > 8 * 1024 * 1024)
326
+ process.kill('SIGKILL');
327
+ });
328
+ process.stdout.on('data', (bytes) => {
329
+ try {
330
+ total += bytes.length;
331
+ if (total > 32 * 1024 * 1024)
332
+ throw Error('Gateway output bound');
333
+ buffer += bytes.toString();
334
+ let end;
335
+ while ((end = buffer.indexOf('\n')) >= 0) {
336
+ const line = buffer.slice(0, end);
337
+ buffer = buffer.slice(end + 1);
338
+ if (line.length > 3 * 1024 * 1024)
339
+ throw Error('Gateway frame bound');
340
+ const m = JSON.parse(line);
341
+ if (m.ready === true && !ready) {
342
+ ready = true;
343
+ clearTimeout(timer);
344
+ resolve();
345
+ continue;
346
+ }
347
+ if (!ready ||
348
+ !Number.isSafeInteger(m.id) ||
349
+ ids.has(m.id) ||
350
+ ids.size >= 256 ||
351
+ inflight >= 8 ||
352
+ typeof m.body !== 'string')
353
+ throw Error('Invalid gateway request');
354
+ ids.add(m.id);
355
+ inflight++;
356
+ void (async () => {
357
+ try {
358
+ const reply = await handler({
359
+ method: m.method,
360
+ path: m.path,
361
+ headers: m.headers,
362
+ body: Buffer.from(m.body, 'base64'),
363
+ });
364
+ send({ id: m.id, status: reply.status, content_type: reply.content_type });
365
+ let bytes = 0;
366
+ const chunks = Buffer.isBuffer(reply.body) ? [reply.body] : reply.body;
367
+ for await (const chunk of chunks) {
368
+ bytes += chunk.length;
369
+ if (bytes > HARNESS_CONTAINER_PROFILE.response_bytes)
370
+ throw Error('Gateway response bound');
371
+ send({ id: m.id, chunk: Buffer.from(chunk).toString('base64') });
372
+ }
373
+ send({ id: m.id, end: true });
374
+ }
375
+ catch {
376
+ send({
377
+ id: m.id,
378
+ status: 503,
379
+ chunk: Buffer.from('{"error":"Gateway request denied"}').toString('base64'),
380
+ end: true,
381
+ });
382
+ }
383
+ finally {
384
+ inflight--;
385
+ }
386
+ })().catch(() => process.kill('SIGKILL'));
387
+ }
388
+ if (buffer.length > 3 * 1024 * 1024)
389
+ throw Error('Gateway frame bound');
390
+ }
391
+ catch (error) {
392
+ clearTimeout(timer);
393
+ reject(error);
394
+ process.kill('SIGKILL');
395
+ }
396
+ });
397
+ process.on('close', () => {
398
+ clearTimeout(timer);
399
+ this.active.delete(receipt.run_id);
400
+ if (!ready)
401
+ reject(Error('Gateway exited before readiness'));
402
+ });
403
+ });
404
+ await started;
405
+ }
406
+ async launchHarness(receipt, dialect) {
407
+ await this.assert(receipt);
408
+ return this.launch(receipt.harness, dialect);
409
+ }
410
+ async cleanup(receipt) {
411
+ const info = engineJson(await this.engine('GET', '/info'));
412
+ if (info.ID !== this.engineId)
413
+ throw new ServiceError('harness_engine_changed', 'Cannot confirm ownership on a changed engine');
414
+ for (const [id, kind] of [
415
+ [receipt.harness, 'worker'],
416
+ [receipt.gateway, 'gateway'],
417
+ ]) {
418
+ const found = await this.engine('GET', `/containers/${id}/json`);
419
+ if (found.status === 404)
420
+ continue;
421
+ await this.owned(id, receipt.run_id, kind);
422
+ const deleted = await this.engine('DELETE', `/containers/${id}?force=true`);
423
+ if (![204, 404].includes(deleted.status))
424
+ throw Error('Harness cleanup unconfirmed');
425
+ }
426
+ this.active.get(receipt.run_id)?.kill('SIGKILL');
427
+ this.active.delete(receipt.run_id);
428
+ }
429
+ /** Lost create replies are reconciled by deterministic names and exact owner/profile labels. */
430
+ async cleanupRun(run) {
431
+ if (!/^[0-9a-f-]{36}$/.test(run))
432
+ throw Error('Invalid owned harness run');
433
+ const info = engineJson(await this.engine('GET', '/info'));
434
+ if (info.ID !== this.engineId)
435
+ throw new ServiceError('harness_engine_changed', 'Cannot reconcile on a changed engine');
436
+ for (const kind of ['worker', 'gateway']) {
437
+ const name = 'salaam-harness-' + run + '-' + kind;
438
+ const found = await this.engine('GET', '/containers/' + name + '/json');
439
+ if (found.status === 404)
440
+ continue;
441
+ const data = engineJson(found);
442
+ await this.owned(String(data.Id), run, kind);
443
+ const removed = await this.engine('DELETE', '/containers/' + data.Id + '?force=true');
444
+ if (![204, 404].includes(removed.status))
445
+ throw Error('Owned harness reconciliation remains uncertain');
446
+ }
447
+ this.active.get(run)?.kill('SIGKILL');
448
+ this.active.delete(run);
449
+ }
450
+ }
@@ -0,0 +1,104 @@
1
+ import { randomUUID, timingSafeEqual } from 'node:crypto';
2
+ import { ServiceError } from './protocol.js';
3
+ /** Text/custom-tool-only Anthropic gateway. The caller owns durable root admission and usage reconciliation. */
4
+ export class AnthropicHarnessGateway {
5
+ policy;
6
+ constructor(policy) {
7
+ this.policy = policy;
8
+ if (!policy.model ||
9
+ !Number.isSafeInteger(policy.output) ||
10
+ policy.output < 1 ||
11
+ policy.output > 8192 ||
12
+ policy.token.length < 32)
13
+ throw new Error('Invalid harness model policy');
14
+ }
15
+ async request(request) {
16
+ const supplied = request.headers['x-api-key'] ?? request.headers.authorization?.replace(/^Bearer /, '') ?? '';
17
+ if (typeof supplied !== 'string' ||
18
+ Buffer.byteLength(supplied) !== Buffer.byteLength(this.policy.token) ||
19
+ !timingSafeEqual(Buffer.from(supplied), Buffer.from(this.policy.token)))
20
+ throw new ServiceError('harness_gateway_auth', 'Unowned gateway request');
21
+ if (request.method !== 'POST' ||
22
+ !['/v1/messages', '/v1/messages?beta=true'].includes(request.path) ||
23
+ request.body.length > 2 * 1024 * 1024)
24
+ throw new ServiceError('harness_gateway_scope', 'Gateway route is outside its admitted scope');
25
+ let body;
26
+ try {
27
+ body = JSON.parse(request.body.toString('utf8'));
28
+ }
29
+ catch {
30
+ throw new ServiceError('harness_gateway_payload', 'Invalid model payload');
31
+ }
32
+ if (!body ||
33
+ Array.isArray(body) ||
34
+ body.model !== this.policy.model ||
35
+ !Number.isSafeInteger(body.max_tokens) ||
36
+ body.max_tokens < 1 ||
37
+ !Array.isArray(body.messages) ||
38
+ body.messages.length > 256 ||
39
+ body.mcp_servers ||
40
+ body.container ||
41
+ body.tools?.some((t) => !t || t.type !== undefined || typeof t.name !== 'string' || !t.input_schema))
42
+ throw new ServiceError('harness_gateway_payload', 'Model route or tool authority differs from its admission');
43
+ const content = (blocks, role) => typeof blocks === 'string' ||
44
+ (Array.isArray(blocks) &&
45
+ blocks.every((b) => b &&
46
+ typeof b === 'object' &&
47
+ ((b.type === 'text' && typeof b.text === 'string') ||
48
+ (role === 'assistant' &&
49
+ (b.type === 'thinking' ||
50
+ b.type === 'redacted_thinking' ||
51
+ b.type === 'tool_use')) ||
52
+ (role === 'user' && b.type === 'tool_result' && content(b.content ?? '', 'tool')))));
53
+ if (body.messages.some((m) => !m || !['user', 'assistant'].includes(m.role) || !content(m.content, m.role)) ||
54
+ (body.system !== undefined && !content(body.system, 'system')))
55
+ throw new ServiceError('harness_gateway_payload', 'Only admitted text and custom tool data may leave the workspace');
56
+ body.max_tokens = Math.min(body.max_tokens, this.policy.output);
57
+ if (body.thinking?.type === 'enabled' && body.thinking.budget_tokens >= body.max_tokens)
58
+ throw new ServiceError('harness_gateway_payload', 'Harness thinking budget exceeds its admitted output cap');
59
+ const credential = await this.policy.credential();
60
+ if (!credential)
61
+ throw new Error('Harness provider credential unavailable');
62
+ const id = 'harness-model-request:' + randomUUID();
63
+ this.policy.admit(id, request.body.length, body.max_tokens);
64
+ const response = await (this.policy.fetch ?? fetch)('https://api.anthropic.com/v1/messages', {
65
+ method: 'POST',
66
+ redirect: 'error',
67
+ signal: AbortSignal.timeout(60000),
68
+ headers: {
69
+ 'content-type': 'application/json',
70
+ 'anthropic-version': '2023-06-01',
71
+ 'x-api-key': credential,
72
+ ...(request.headers['anthropic-beta']
73
+ ? { 'anthropic-beta': request.headers['anthropic-beta'] }
74
+ : {}),
75
+ },
76
+ body: JSON.stringify(body),
77
+ });
78
+ const reader = response.body?.getReader();
79
+ const chunks = async function* () {
80
+ let size = 0;
81
+ try {
82
+ if (!reader)
83
+ return;
84
+ while (true) {
85
+ const chunk = await reader.read();
86
+ if (chunk.done)
87
+ break;
88
+ size += chunk.value.length;
89
+ if (size > 8 * 1024 * 1024)
90
+ throw new Error('Harness provider response exceeded its bound');
91
+ yield chunk.value;
92
+ }
93
+ }
94
+ finally {
95
+ await reader?.cancel();
96
+ }
97
+ };
98
+ return {
99
+ status: response.status,
100
+ content_type: response.headers.get('content-type') ?? 'application/json',
101
+ body: chunks(),
102
+ };
103
+ }
104
+ }
@@ -0,0 +1,88 @@
1
+ import { mkdtempSync, mkdirSync, readFileSync, writeFileSync, existsSync, realpathSync, renameSync, rmSync, createWriteStream, } from 'node:fs';
2
+ import { join, dirname, resolve } from 'node:path';
3
+ import { execFile } from 'node:child_process';
4
+ import { promisify } from 'node:util';
5
+ import { createHash } from 'node:crypto';
6
+ import { Readable, Transform } from 'node:stream';
7
+ import { pipeline } from 'node:stream/promises';
8
+ import { ROOT } from './config.js';
9
+ import { harnessRuntimeDigest, assertHarnessArtifactRoot } from './harness-backend.js';
10
+ /** Explicit operator setup, outside all worker turns. Fixed lock, no lifecycle scripts or inherited credentials. */
11
+ export async function prepareHarnessRuntime(destination, arch) {
12
+ if (!['linux-arm64', 'linux-x64'].includes(arch))
13
+ throw Error('Select --arch linux-arm64 or linux-x64 for the Docker execution host');
14
+ const output = resolve(destination);
15
+ if (existsSync(output))
16
+ throw Error('Runtime destination already exists; choose a new directory for renewed admission');
17
+ const parent = realpathSync(dirname(output)), temp = mkdtempSync(join(parent, '.salaam-runtime-')), bundle = join(temp, 'bundle');
18
+ const exec = promisify(execFile), home = join(temp, 'home');
19
+ try {
20
+ for (const p of [bundle, home])
21
+ mkdirSync(p, { mode: 0o700 });
22
+ for (const file of ['package.json', 'package-lock.json'])
23
+ writeFileSync(join(bundle, file), readFileSync(join(ROOT, 'harness-runtime', file)), {
24
+ mode: 0o600,
25
+ });
26
+ for (const name of ['user.npmrc', 'global.npmrc'])
27
+ writeFileSync(join(temp, name), '', { mode: 0o600 });
28
+ const env = {
29
+ HOME: home,
30
+ PATH: dirname(process.execPath) + ':/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin',
31
+ NPM_CONFIG_USERCONFIG: join(temp, 'user.npmrc'),
32
+ NPM_CONFIG_GLOBALCONFIG: join(temp, 'global.npmrc'),
33
+ NPM_CONFIG_CACHE: join(temp, 'cache'),
34
+ };
35
+ await exec('npm', [
36
+ 'ci',
37
+ '--ignore-scripts',
38
+ '--no-audit',
39
+ '--no-fund',
40
+ '--registry=https://registry.npmjs.org',
41
+ ], { cwd: bundle, env, timeout: 300000, maxBuffer: 4 * 1024 * 1024 });
42
+ const pin = JSON.parse(readFileSync(join(ROOT, 'harness-runtime/codex-pins.json'), 'utf8'))[arch];
43
+ const response = await fetch(pin.url, {
44
+ redirect: 'error',
45
+ signal: AbortSignal.timeout(120000),
46
+ });
47
+ if (!response.ok || !response.body)
48
+ throw Error('Pinned Codex archive download failed');
49
+ const archive = join(temp, 'codex.tgz'), hash = createHash('sha512');
50
+ let size = 0;
51
+ await pipeline(Readable.fromWeb(response.body), new Transform({
52
+ transform(chunk, _encoding, done) {
53
+ size += chunk.length;
54
+ if (size > 1024 * 1024 * 1024)
55
+ return done(Error('Harness archive exceeds bound'));
56
+ hash.update(chunk);
57
+ done(null, chunk);
58
+ },
59
+ }), createWriteStream(archive, { flags: 'wx', mode: 0o600 }));
60
+ if (hash.digest('base64') !== pin.integrity)
61
+ throw Error('Pinned Codex archive integrity mismatch');
62
+ const listing = await exec('tar', ['-tzf', archive], {
63
+ env,
64
+ timeout: 30000,
65
+ maxBuffer: 1048576,
66
+ });
67
+ if (listing.stdout
68
+ .split('\n')
69
+ .filter(Boolean)
70
+ .some((path) => !path.startsWith('package/') || path.split('/').includes('..')))
71
+ throw Error('Unsafe pinned archive layout');
72
+ mkdirSync(join(bundle, 'codex'), { mode: 0o700 });
73
+ await exec('tar', ['-xzf', archive, '--strip-components=1', '-C', join(bundle, 'codex')], {
74
+ env,
75
+ timeout: 120000,
76
+ maxBuffer: 1048576,
77
+ });
78
+ assertHarnessArtifactRoot(bundle);
79
+ const hashValue = await harnessRuntimeDigest(bundle);
80
+ if (existsSync(output))
81
+ throw Error('Runtime destination appeared during setup');
82
+ renameSync(bundle, output);
83
+ return { root: output, digest: hashValue, arch };
84
+ }
85
+ finally {
86
+ rmSync(temp, { recursive: true, force: true });
87
+ }
88
+ }