@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
package/src/cli.ts
CHANGED
|
@@ -1,24 +1,54 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { acp } from './acp.js';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
|
-
import { join, resolve } from 'node:path';
|
|
4
|
+
import { join, resolve, dirname } from 'node:path';
|
|
5
5
|
import { spawn } from 'node:child_process';
|
|
6
|
-
import { randomUUID } from 'node:crypto';
|
|
6
|
+
import { randomUUID, randomBytes } from 'node:crypto';
|
|
7
|
+
import { digest, object } from './protocol.js';
|
|
7
8
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
mkdirSync,
|
|
11
|
+
openSync,
|
|
12
|
+
closeSync,
|
|
13
|
+
readFileSync,
|
|
14
|
+
realpathSync,
|
|
15
|
+
writeFileSync,
|
|
16
|
+
fsyncSync,
|
|
17
|
+
} from 'node:fs';
|
|
9
18
|
import { ROOT, VERSION } from './config.js';
|
|
10
19
|
import { cliHelp, hasHelpFlag } from './cli-help.js';
|
|
11
20
|
import { SalaamService } from './service.js';
|
|
12
21
|
import { call } from './transport.js';
|
|
13
22
|
import { attach, acquireControl, Outbox } from './attach.js';
|
|
14
23
|
import type { SessionSnapshot, Approval, Operation } from './protocol.js';
|
|
15
|
-
import {
|
|
16
|
-
|
|
24
|
+
import {
|
|
25
|
+
providerRoute,
|
|
26
|
+
PI_PROVIDERS,
|
|
27
|
+
getProvider,
|
|
28
|
+
providerHasOutputCap,
|
|
29
|
+
} from './provider-config.js';
|
|
30
|
+
import { readSecret, validateApiKey } from './credentials.js';
|
|
31
|
+
import {
|
|
32
|
+
authDirectory,
|
|
33
|
+
ProviderCredentialStore,
|
|
34
|
+
providerModels,
|
|
35
|
+
loginProvider,
|
|
36
|
+
resolveProviderAuth,
|
|
37
|
+
} from './provider-auth.js';
|
|
38
|
+
import { bindProviderConfiguration } from './provider-model.js';
|
|
39
|
+
import { createInterface } from 'node:readline/promises';
|
|
40
|
+
import type { AuthInteraction } from '@earendil-works/pi-ai';
|
|
17
41
|
import { canonical } from './protocol.js';
|
|
18
42
|
import { SocketEffectConnector } from './effect-connector.js';
|
|
19
43
|
import { proveExecutionBoundary } from './execution-proof.js';
|
|
20
44
|
import { BoundaryBroker, engineRequest } from './execution-boundary.js';
|
|
21
45
|
import { workspaceDiff, type WorkspaceChange } from './workspace-diff.js';
|
|
46
|
+
import {
|
|
47
|
+
probeHarness,
|
|
48
|
+
HARNESS_TARGETS,
|
|
49
|
+
type HarnessPin,
|
|
50
|
+
type HarnessTarget,
|
|
51
|
+
} from './harness-probe.js';
|
|
22
52
|
interface WorkspaceDiffPage {
|
|
23
53
|
revision: number;
|
|
24
54
|
digest: string;
|
|
@@ -63,6 +93,406 @@ async function main(): Promise<void> {
|
|
|
63
93
|
if (foreground) args.splice(args.indexOf('--foreground'), 1);
|
|
64
94
|
const socket = join(state, 'operator.sock');
|
|
65
95
|
const [group, verb, id] = args;
|
|
96
|
+
if (group === 'a2a' && verb === 'credential') {
|
|
97
|
+
const file = option('--file');
|
|
98
|
+
if (!id || !file) throw Error('Supply PEER_ID and --file NEW_OWNER_ONLY_FILE');
|
|
99
|
+
const { a2aIdentity } = await import('./a2a-peer.js');
|
|
100
|
+
a2aIdentity(id);
|
|
101
|
+
const path = resolve(file),
|
|
102
|
+
value = { peer: id, token: randomBytes(32).toString('base64url') };
|
|
103
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
104
|
+
const fd = openSync(path, 'wx', 0o600);
|
|
105
|
+
try {
|
|
106
|
+
writeFileSync(fd, JSON.stringify(value) + '\n');
|
|
107
|
+
fsyncSync(fd);
|
|
108
|
+
} finally {
|
|
109
|
+
closeSync(fd);
|
|
110
|
+
}
|
|
111
|
+
console.log(
|
|
112
|
+
JSON.stringify(
|
|
113
|
+
{ peer: id, file: realpathSync(path), credential_digest: digest(value) },
|
|
114
|
+
null,
|
|
115
|
+
2,
|
|
116
|
+
),
|
|
117
|
+
);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (group === 'a2a' && verb === 'discover') {
|
|
121
|
+
const peer = option('--peer'),
|
|
122
|
+
endpoint = option('--endpoint'),
|
|
123
|
+
card_url = option('--card-url'),
|
|
124
|
+
credential = option('--credential-file'),
|
|
125
|
+
addresses = option('--addresses');
|
|
126
|
+
if (!peer || !endpoint || !card_url || !credential || !addresses)
|
|
127
|
+
throw Error('Supply --peer, --endpoint, --card-url, --credential-file and --addresses');
|
|
128
|
+
const { privateJSON, validatePeer, A2AHttp } = await import('./a2a-peer.js');
|
|
129
|
+
const credential_file = realpathSync(resolve(credential)),
|
|
130
|
+
value = privateJSON(credential_file);
|
|
131
|
+
const pin = validatePeer({
|
|
132
|
+
id: peer,
|
|
133
|
+
endpoint,
|
|
134
|
+
card_url,
|
|
135
|
+
card_digest: digest(null),
|
|
136
|
+
tenant: option('--tenant') ?? '',
|
|
137
|
+
skill: 'pending-review',
|
|
138
|
+
addresses: addresses.split(','),
|
|
139
|
+
local: args.includes('--local'),
|
|
140
|
+
credential_file,
|
|
141
|
+
credential_digest: digest(value),
|
|
142
|
+
files: [],
|
|
143
|
+
retention: 'unknown',
|
|
144
|
+
cancel: false,
|
|
145
|
+
streaming: false,
|
|
146
|
+
});
|
|
147
|
+
console.log(JSON.stringify(await new A2AHttp(pin).request(card_url), null, 2));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (group === 'a2a' && verb === 'pin') {
|
|
151
|
+
const peer = option('--peer'),
|
|
152
|
+
endpoint = option('--endpoint'),
|
|
153
|
+
cardFile = option('--card-file'),
|
|
154
|
+
credential = option('--credential-file'),
|
|
155
|
+
addresses = option('--addresses'),
|
|
156
|
+
skill = option('--skill');
|
|
157
|
+
if (!peer || !endpoint || !cardFile || !credential || !addresses || !skill)
|
|
158
|
+
throw Error(
|
|
159
|
+
'Supply --peer, --endpoint, --card-file, --credential-file, --addresses and --skill',
|
|
160
|
+
);
|
|
161
|
+
const { privateJSON, validatePeer, A2AHttp } = await import('./a2a-peer.js');
|
|
162
|
+
const card = JSON.parse(readFileSync(resolve(cardFile), 'utf8')),
|
|
163
|
+
credential_file = realpathSync(resolve(credential)),
|
|
164
|
+
value = privateJSON(credential_file),
|
|
165
|
+
card_url = option('--card-url');
|
|
166
|
+
if (!card_url) throw Error('Supply the exact --card-url that was reviewed');
|
|
167
|
+
const pin = validatePeer({
|
|
168
|
+
id: peer,
|
|
169
|
+
endpoint,
|
|
170
|
+
card_url,
|
|
171
|
+
card_digest: digest(card),
|
|
172
|
+
tenant: option('--tenant') ?? '',
|
|
173
|
+
skill,
|
|
174
|
+
addresses: addresses.split(','),
|
|
175
|
+
local: args.includes('--local'),
|
|
176
|
+
credential_file,
|
|
177
|
+
credential_digest: digest(value),
|
|
178
|
+
files: option('--files')?.split(',').filter(Boolean) ?? [],
|
|
179
|
+
retention: option('--retention') ?? 'unknown',
|
|
180
|
+
cancel: args.includes('--supports-cancel'),
|
|
181
|
+
streaming: args.includes('--supports-streaming'),
|
|
182
|
+
});
|
|
183
|
+
new A2AHttp(pin).assertCard(card);
|
|
184
|
+
console.log(JSON.stringify(pin, null, 2));
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (group === 'a2a' && verb === 'grant') {
|
|
188
|
+
const assignment = option('--assignment'),
|
|
189
|
+
peer = option('--peer'),
|
|
190
|
+
credential = option('--credential-file');
|
|
191
|
+
if (!assignment || !peer || !credential)
|
|
192
|
+
throw Error('Supply --assignment, --peer and --credential-file');
|
|
193
|
+
const { privateJSON } = await import('./a2a-peer.js');
|
|
194
|
+
const credential_file = realpathSync(resolve(credential)),
|
|
195
|
+
value = object(privateJSON(credential_file));
|
|
196
|
+
if (
|
|
197
|
+
value.peer !== peer ||
|
|
198
|
+
Object.keys(value).sort().join(',') !== 'peer,token' ||
|
|
199
|
+
typeof value.token !== 'string' ||
|
|
200
|
+
!/^[A-Za-z0-9._~-]{32,512}$/.test(value.token)
|
|
201
|
+
)
|
|
202
|
+
throw Error('Use a dedicated peer credential JSON {peer,token}');
|
|
203
|
+
const peerClient = option('--peer-client');
|
|
204
|
+
if (peerClient && peerClient !== 'openclaw-2026.10.5')
|
|
205
|
+
throw Error('Only the explicitly pinned OpenClaw client adapter is available');
|
|
206
|
+
const binding = await call(socket, `/v1/a2a/grants/${encodeURIComponent(assignment)}`);
|
|
207
|
+
console.log(
|
|
208
|
+
JSON.stringify(
|
|
209
|
+
{
|
|
210
|
+
peer,
|
|
211
|
+
tenant: option('--tenant') ?? '',
|
|
212
|
+
skill: option('--skill') ?? 'review',
|
|
213
|
+
assignment_id: assignment,
|
|
214
|
+
binding_digest: digest(binding),
|
|
215
|
+
credential_file,
|
|
216
|
+
credential_digest: digest(value),
|
|
217
|
+
...(peerClient ? { client: peerClient } : {}),
|
|
218
|
+
},
|
|
219
|
+
null,
|
|
220
|
+
2,
|
|
221
|
+
),
|
|
222
|
+
);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (group === 'a2a' && verb === 'peers') {
|
|
226
|
+
console.log(
|
|
227
|
+
JSON.stringify(
|
|
228
|
+
(await call<any[]>(socket, '/v1/harnesses')).filter((r) => r.kind === 'a2a'),
|
|
229
|
+
null,
|
|
230
|
+
2,
|
|
231
|
+
),
|
|
232
|
+
);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (group === 'harness' && verb === 'probe') {
|
|
236
|
+
const path = option('--pin');
|
|
237
|
+
if (!path) throw new Error('Supply an operator-reviewed --pin FILE');
|
|
238
|
+
const pin = JSON.parse(readFileSync(resolve(path), 'utf8')) as HarnessPin;
|
|
239
|
+
console.log(JSON.stringify(await probeHarness(pin), null, 2));
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (group === 'harness' && verb === 'prepare') {
|
|
243
|
+
const path = option('--runtime-root'),
|
|
244
|
+
arch = option('--arch');
|
|
245
|
+
if (!path || !arch) throw Error('Supply a new --runtime-root and execution-host --arch');
|
|
246
|
+
const { prepareHarnessRuntime } = await import('./harness-prepare.js');
|
|
247
|
+
console.log(JSON.stringify(await prepareHarnessRuntime(path, arch), null, 2));
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (group === 'harness' && verb === 'config') {
|
|
251
|
+
const target = option('--target'),
|
|
252
|
+
path = option('--runtime-root');
|
|
253
|
+
if (!target || !Object.hasOwn(HARNESS_TARGETS, target) || !path)
|
|
254
|
+
throw Error('Supply a named --target and dedicated --runtime-root');
|
|
255
|
+
const runtime_root = realpathSync(resolve(path));
|
|
256
|
+
const { assertHarnessArtifactRoot, harnessRuntimeDigest } =
|
|
257
|
+
await import('./harness-backend.js');
|
|
258
|
+
assertHarnessArtifactRoot(runtime_root);
|
|
259
|
+
console.log(
|
|
260
|
+
JSON.stringify(
|
|
261
|
+
{
|
|
262
|
+
schema: 1,
|
|
263
|
+
runtimes: [
|
|
264
|
+
{
|
|
265
|
+
id: option('--id') ?? target,
|
|
266
|
+
target,
|
|
267
|
+
version: HARNESS_TARGETS[target as HarnessTarget].version,
|
|
268
|
+
runtime_root,
|
|
269
|
+
runtime_digest: await harnessRuntimeDigest(runtime_root),
|
|
270
|
+
model: 'claude-sonnet-4-6',
|
|
271
|
+
output: 4096,
|
|
272
|
+
requests: 12,
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
},
|
|
276
|
+
null,
|
|
277
|
+
2,
|
|
278
|
+
),
|
|
279
|
+
);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (
|
|
283
|
+
['harness', 'a2a'].includes(group ?? '') &&
|
|
284
|
+
['list', 'inspect', 'reconcile'].includes(verb ?? '')
|
|
285
|
+
) {
|
|
286
|
+
if (verb === 'list') {
|
|
287
|
+
console.log(JSON.stringify(await call(socket, '/v1/harnesses'), null, 2));
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (!id) throw Error('Supply an external CHILD_ID');
|
|
291
|
+
const current = await call<any>(socket, `/v1/harnesses/${id}`);
|
|
292
|
+
if (verb === 'inspect') {
|
|
293
|
+
console.log(JSON.stringify(current, null, 2));
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const run_id = option('--run-id');
|
|
297
|
+
if (!run_id) throw Error('Supply the exact --run-id from harness inspect');
|
|
298
|
+
const client_id = randomUUID(),
|
|
299
|
+
owner = current.child.parent_session,
|
|
300
|
+
lease = await acquireControl(socket, owner, client_id, args.includes('--takeover'));
|
|
301
|
+
try {
|
|
302
|
+
console.log(
|
|
303
|
+
JSON.stringify(
|
|
304
|
+
await call(socket, `/v1/harnesses/${id}/reconcile`, {
|
|
305
|
+
client_id,
|
|
306
|
+
token: lease.token,
|
|
307
|
+
run_id,
|
|
308
|
+
request_id: option('--request-id') ?? randomUUID(),
|
|
309
|
+
}),
|
|
310
|
+
null,
|
|
311
|
+
2,
|
|
312
|
+
),
|
|
313
|
+
);
|
|
314
|
+
} finally {
|
|
315
|
+
await call(socket, `/v1/sessions/${owner}/control`, {
|
|
316
|
+
request_id: randomUUID(),
|
|
317
|
+
client_id,
|
|
318
|
+
token: lease.token,
|
|
319
|
+
action: 'release',
|
|
320
|
+
expected_epoch: lease.epoch,
|
|
321
|
+
}).catch(() => {});
|
|
322
|
+
}
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (group === 'workers') {
|
|
326
|
+
if (!id) throw new Error('Supply a child or integration receipt ID');
|
|
327
|
+
if (verb === 'proposal' || verb === 'integration') {
|
|
328
|
+
const result = await call<any>(
|
|
329
|
+
socket,
|
|
330
|
+
verb === 'proposal'
|
|
331
|
+
? `/v1/workers/${id}/proposal?offset=${Number(option('--offset') ?? 0)}`
|
|
332
|
+
: `/v1/integrations/${id}`,
|
|
333
|
+
);
|
|
334
|
+
if (verb === 'proposal' && !args.includes('--json')) {
|
|
335
|
+
const { changes, diff, ...metadata } = result;
|
|
336
|
+
console.log(
|
|
337
|
+
JSON.stringify(
|
|
338
|
+
{
|
|
339
|
+
...metadata,
|
|
340
|
+
files: changes.map((c: any) => ({
|
|
341
|
+
path: c.path,
|
|
342
|
+
before_hash: digest(c.before),
|
|
343
|
+
after_hash: digest(c.after),
|
|
344
|
+
})),
|
|
345
|
+
},
|
|
346
|
+
null,
|
|
347
|
+
2,
|
|
348
|
+
),
|
|
349
|
+
);
|
|
350
|
+
for (const patch of diff) console.log(patch);
|
|
351
|
+
} else console.log(JSON.stringify(result, null, 2));
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
if (!['integrate', 'resolve'].includes(verb ?? ''))
|
|
355
|
+
throw new Error('Use workers proposal, integrate, integration or resolve');
|
|
356
|
+
const owner =
|
|
357
|
+
verb === 'integrate'
|
|
358
|
+
? (await call<{ parent_session: string }>(socket, `/v1/children/${id}`)).parent_session
|
|
359
|
+
: (await call<{ parent_session: string }>(socket, `/v1/integrations/${id}`)).parent_session;
|
|
360
|
+
const revision = option('--revision'),
|
|
361
|
+
workspace_digest = option('--root-digest'),
|
|
362
|
+
patch_digest = option('--patch-digest');
|
|
363
|
+
if (revision === undefined || !workspace_digest || (verb === 'integrate' && !patch_digest))
|
|
364
|
+
throw new Error(
|
|
365
|
+
'Supply reviewed --revision N, --root-digest SHA256 and --patch-digest SHA256 for integration',
|
|
366
|
+
);
|
|
367
|
+
const client_id = randomUUID(),
|
|
368
|
+
lease = await acquireControl(socket, owner, client_id, args.includes('--takeover'));
|
|
369
|
+
try {
|
|
370
|
+
const result = await call(
|
|
371
|
+
socket,
|
|
372
|
+
verb === 'integrate' ? `/v1/workers/${id}/integrate` : `/v1/integrations/${id}/resolve`,
|
|
373
|
+
{
|
|
374
|
+
client_id,
|
|
375
|
+
token: lease.token,
|
|
376
|
+
request_id: option('--request-id') ?? randomUUID(),
|
|
377
|
+
workspace_revision: Number(revision),
|
|
378
|
+
workspace_digest,
|
|
379
|
+
patch_digest,
|
|
380
|
+
},
|
|
381
|
+
);
|
|
382
|
+
console.log(JSON.stringify(result, null, 2));
|
|
383
|
+
} finally {
|
|
384
|
+
await call(socket, `/v1/sessions/${owner}/control`, {
|
|
385
|
+
request_id: randomUUID(),
|
|
386
|
+
client_id,
|
|
387
|
+
token: lease.token,
|
|
388
|
+
action: 'release',
|
|
389
|
+
expected_epoch: lease.epoch,
|
|
390
|
+
}).catch(() => {});
|
|
391
|
+
}
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (group === 'team') {
|
|
395
|
+
if (!id) throw new Error('Supply TASK_ID');
|
|
396
|
+
if (verb === 'show') {
|
|
397
|
+
console.log(JSON.stringify(await call(socket, `/v1/tasks/${id}/team`), null, 2));
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
if (!['assign', 'run', 'reject', 'send', 'receive', 'ack'].includes(verb ?? ''))
|
|
401
|
+
throw new Error('Use team show, assign, run, send, receive or ack');
|
|
402
|
+
const task = await call<{ session_id: string }>(socket, `/v1/tasks/${id}`);
|
|
403
|
+
const client_id = randomUUID(),
|
|
404
|
+
lease = await acquireControl(socket, task.session_id, client_id, args.includes('--takeover'));
|
|
405
|
+
try {
|
|
406
|
+
const assignment = option('--assignment');
|
|
407
|
+
if (['run', 'reject', 'receive', 'ack'].includes(verb!) && !assignment)
|
|
408
|
+
throw new Error('Supply --assignment ID');
|
|
409
|
+
const revision = option('--revision'),
|
|
410
|
+
generation = option('--generation');
|
|
411
|
+
if (['assign', 'run', 'reject'].includes(verb!) && revision === undefined)
|
|
412
|
+
throw new Error('Supply the reviewed --revision N');
|
|
413
|
+
if (['run', 'reject'].includes(verb!) && generation === undefined)
|
|
414
|
+
throw new Error('Supply the reviewed --generation N');
|
|
415
|
+
const path = assignment ? `${assignment}/${verb}` : verb;
|
|
416
|
+
const result = await call(socket, `/v1/tasks/${id}/team/${path}`, {
|
|
417
|
+
client_id,
|
|
418
|
+
token: lease.token,
|
|
419
|
+
request_id: option('--request-id') ?? randomUUID(),
|
|
420
|
+
expected_revision: revision === undefined ? undefined : Number(revision),
|
|
421
|
+
generation: generation === undefined ? undefined : Number(generation),
|
|
422
|
+
...(verb === 'assign'
|
|
423
|
+
? {
|
|
424
|
+
step_id: option('--step'),
|
|
425
|
+
profile_id: option('--profile'),
|
|
426
|
+
...(() => {
|
|
427
|
+
const runtime = option('--runtime');
|
|
428
|
+
return runtime
|
|
429
|
+
? { runtime, invocations: Number(option('--invocations')) }
|
|
430
|
+
: { requests: Number(option('--requests')) };
|
|
431
|
+
})(),
|
|
432
|
+
}
|
|
433
|
+
: {}),
|
|
434
|
+
...(verb === 'run'
|
|
435
|
+
? { instruction: option('--instruction'), continues_attempt_id: option('--continues') }
|
|
436
|
+
: {}),
|
|
437
|
+
...(verb === 'send' ? { recipient: option('--to'), text: option('--text') } : {}),
|
|
438
|
+
...(verb === 'ack' ? { message_id: option('--message') } : {}),
|
|
439
|
+
...(verb === 'reject' ? { reason: option('--reason') } : {}),
|
|
440
|
+
});
|
|
441
|
+
console.log(JSON.stringify(result, null, 2));
|
|
442
|
+
} finally {
|
|
443
|
+
await call(socket, `/v1/sessions/${task.session_id}/control`, {
|
|
444
|
+
request_id: randomUUID(),
|
|
445
|
+
client_id,
|
|
446
|
+
token: lease.token,
|
|
447
|
+
action: 'release',
|
|
448
|
+
expected_epoch: lease.epoch,
|
|
449
|
+
}).catch(() => {});
|
|
450
|
+
}
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
if (group === 'agents') {
|
|
454
|
+
const session = option('--session');
|
|
455
|
+
if (!session) throw new Error('Supply --session SESSION_ID');
|
|
456
|
+
if (verb === 'list' || verb === 'inspect') {
|
|
457
|
+
console.log(
|
|
458
|
+
JSON.stringify(
|
|
459
|
+
await call(
|
|
460
|
+
socket,
|
|
461
|
+
`/v1/sessions/${session}/agents${verb === 'inspect' ? '?profile=' + encodeURIComponent(id ?? '') : ''}`,
|
|
462
|
+
),
|
|
463
|
+
null,
|
|
464
|
+
2,
|
|
465
|
+
),
|
|
466
|
+
);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
if (!id || !['admit', 'revoke'].includes(verb ?? ''))
|
|
470
|
+
throw new Error('Use agents list, inspect, admit or revoke');
|
|
471
|
+
const client_id = randomUUID(),
|
|
472
|
+
lease = await acquireControl(socket, session, client_id, args.includes('--takeover'));
|
|
473
|
+
try {
|
|
474
|
+
const expected_digest = option('--digest');
|
|
475
|
+
if (verb === 'admit' && !expected_digest)
|
|
476
|
+
throw new Error('Review agents inspect and supply --digest SHA256');
|
|
477
|
+
const fact_ids = option('--facts')?.split(',').filter(Boolean) ?? [];
|
|
478
|
+
const result = await call(socket, `/v1/sessions/${session}/agents/${verb}`, {
|
|
479
|
+
request_id: option('--request-id') ?? randomUUID(),
|
|
480
|
+
client_id,
|
|
481
|
+
token: lease.token,
|
|
482
|
+
...(verb === 'admit' ? { profile_id: id, expected_digest, fact_ids } : { grant_id: id }),
|
|
483
|
+
});
|
|
484
|
+
console.log(JSON.stringify(result, null, 2));
|
|
485
|
+
} finally {
|
|
486
|
+
await call(socket, `/v1/sessions/${session}/control`, {
|
|
487
|
+
request_id: randomUUID(),
|
|
488
|
+
client_id,
|
|
489
|
+
token: lease.token,
|
|
490
|
+
action: 'release',
|
|
491
|
+
expected_epoch: lease.epoch,
|
|
492
|
+
}).catch(() => {});
|
|
493
|
+
}
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
66
496
|
if (group === 'host') {
|
|
67
497
|
const { planHost, readHost, hostUnit, runHost } = await import('./host.js');
|
|
68
498
|
if (verb === 'plan') {
|
|
@@ -300,11 +730,148 @@ async function main(): Promise<void> {
|
|
|
300
730
|
await acp(state, { coding: args.includes('--coding') });
|
|
301
731
|
return;
|
|
302
732
|
}
|
|
303
|
-
if (group === '
|
|
304
|
-
|
|
305
|
-
|
|
733
|
+
if (group === 'providers' && verb === 'list') {
|
|
734
|
+
console.log(
|
|
735
|
+
JSON.stringify(
|
|
736
|
+
PI_PROVIDERS.map((p) => ({
|
|
737
|
+
id: p.id,
|
|
738
|
+
name: p.name,
|
|
739
|
+
chat_models: p.getModels().length,
|
|
740
|
+
apis: [...new Set(p.getModels().map((m) => m.api))],
|
|
741
|
+
auth: [p.auth.apiKey ? 'api_key' : null, p.auth.oauth ? 'oauth' : null].filter(Boolean),
|
|
742
|
+
limitation: !p.getModels().length
|
|
743
|
+
? 'Classifier only; no chat route'
|
|
744
|
+
: p.id === 'openai-codex'
|
|
745
|
+
? 'No provider-enforced output cap; tasks must explicitly disable token budgeting'
|
|
746
|
+
: null,
|
|
747
|
+
})),
|
|
748
|
+
null,
|
|
749
|
+
2,
|
|
750
|
+
),
|
|
751
|
+
);
|
|
306
752
|
return;
|
|
307
753
|
}
|
|
754
|
+
if (group === 'models' && verb === 'list') {
|
|
755
|
+
const providers = id ? [getProvider(id)] : PI_PROVIDERS;
|
|
756
|
+
if (providers.some((p) => !p)) throw new Error('Unknown Pi provider');
|
|
757
|
+
console.log(
|
|
758
|
+
JSON.stringify(
|
|
759
|
+
providers.flatMap((p) =>
|
|
760
|
+
p!.getModels().map((m) => ({
|
|
761
|
+
provider: p!.id,
|
|
762
|
+
id: m.id,
|
|
763
|
+
name: m.name,
|
|
764
|
+
api: m.api,
|
|
765
|
+
context_window: m.contextWindow,
|
|
766
|
+
max_output_tokens: m.maxTokens,
|
|
767
|
+
})),
|
|
768
|
+
),
|
|
769
|
+
null,
|
|
770
|
+
2,
|
|
771
|
+
),
|
|
772
|
+
);
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
if (group === 'auth') {
|
|
776
|
+
const store = new ProviderCredentialStore();
|
|
777
|
+
if (verb === 'status') {
|
|
778
|
+
const models = providerModels(store),
|
|
779
|
+
entries = id ? [getProvider(id)] : PI_PROVIDERS;
|
|
780
|
+
if (entries.some((p) => !p)) throw new Error('Unknown Pi provider');
|
|
781
|
+
const statuses = [];
|
|
782
|
+
for (const p of entries) {
|
|
783
|
+
try {
|
|
784
|
+
const auth = await models.checkAuth(p!.id);
|
|
785
|
+
statuses.push({
|
|
786
|
+
provider: p!.id,
|
|
787
|
+
configured: !!auth,
|
|
788
|
+
type: auth?.type ?? null,
|
|
789
|
+
source: auth?.source ?? null,
|
|
790
|
+
});
|
|
791
|
+
} catch {
|
|
792
|
+
statuses.push({ provider: p!.id, configured: false, error: 'Credential check failed' });
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
console.log(JSON.stringify(statuses, null, 2));
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
if (!id || !getProvider(id)) throw new Error('Choose a provider from salaam providers list');
|
|
799
|
+
if (verb === 'logout') {
|
|
800
|
+
await store.delete(id);
|
|
801
|
+
console.log(
|
|
802
|
+
'Stored credential removed for ' + id + '. Environment credentials remain external.',
|
|
803
|
+
);
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
if (verb === 'set') {
|
|
807
|
+
if (!getProvider(id)!.auth.apiKey)
|
|
808
|
+
throw new Error('This provider needs salaam auth login ' + id + ' --oauth');
|
|
809
|
+
const key = (await readSecret(id + ' API key (hidden): ')).trim();
|
|
810
|
+
if (
|
|
811
|
+
!key ||
|
|
812
|
+
key.length > 4096 ||
|
|
813
|
+
/[^\x21-\x7e]/.test(key) ||
|
|
814
|
+
(id === 'anthropic' && key.includes('sk-ant-oat'))
|
|
815
|
+
)
|
|
816
|
+
throw new Error('Supply a provider API key, not an OAuth token');
|
|
817
|
+
if (id === 'anthropic') validateApiKey(key);
|
|
818
|
+
await store.modify(id, async () => ({ type: 'api_key', key }));
|
|
819
|
+
console.log(
|
|
820
|
+
'API key saved privately for ' + id + ' under ' + authDirectory() + ' (mode 600).',
|
|
821
|
+
);
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
if (verb === 'login') {
|
|
825
|
+
if (!process.stdin.isTTY)
|
|
826
|
+
throw new Error(
|
|
827
|
+
'Interactive login needs a terminal; use auth set PROVIDER for a piped API key',
|
|
828
|
+
);
|
|
829
|
+
const provider = getProvider(id)!;
|
|
830
|
+
const type = args.includes('--oauth') || !provider.auth.apiKey ? 'oauth' : 'api_key';
|
|
831
|
+
const interaction: AuthInteraction = {
|
|
832
|
+
async prompt(prompt) {
|
|
833
|
+
if (prompt.type === 'secret')
|
|
834
|
+
return readSecret(prompt.message + ' (hidden): ', prompt.signal);
|
|
835
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
836
|
+
try {
|
|
837
|
+
const choices =
|
|
838
|
+
prompt.type === 'select'
|
|
839
|
+
? '\n' + prompt.options.map((v, i) => `${i + 1}. ${v.label} (${v.id})`).join('\n')
|
|
840
|
+
: '';
|
|
841
|
+
const answer = await rl.question(prompt.message + choices + '\n> ', {
|
|
842
|
+
signal: prompt.signal,
|
|
843
|
+
});
|
|
844
|
+
return prompt.type === 'select' && /^\d+$/.test(answer)
|
|
845
|
+
? (prompt.options[Number(answer) - 1]?.id ?? answer)
|
|
846
|
+
: answer;
|
|
847
|
+
} finally {
|
|
848
|
+
rl.close();
|
|
849
|
+
}
|
|
850
|
+
},
|
|
851
|
+
notify(event) {
|
|
852
|
+
if (event.type === 'auth_url')
|
|
853
|
+
console.error(
|
|
854
|
+
'Open in your browser: ' +
|
|
855
|
+
event.url +
|
|
856
|
+
(event.instructions ? '\n' + event.instructions : ''),
|
|
857
|
+
);
|
|
858
|
+
else if (event.type === 'device_code')
|
|
859
|
+
console.error('Open ' + event.verificationUri + ' and enter ' + event.userCode);
|
|
860
|
+
else console.error(event.message);
|
|
861
|
+
},
|
|
862
|
+
};
|
|
863
|
+
await loginProvider(id, type, interaction);
|
|
864
|
+
console.log(
|
|
865
|
+
'Signed in to ' +
|
|
866
|
+
id +
|
|
867
|
+
' with ' +
|
|
868
|
+
type +
|
|
869
|
+
'. Restart the service to use a different provider route.',
|
|
870
|
+
);
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
throw new Error('Run salaam auth --help');
|
|
874
|
+
}
|
|
308
875
|
if (group === 'schedules' && verb) {
|
|
309
876
|
if (verb === 'list' || (id && ['inspect', 'occurrences'].includes(verb))) {
|
|
310
877
|
console.log(
|
|
@@ -874,26 +1441,42 @@ async function main(): Promise<void> {
|
|
|
874
1441
|
const model = option('--model');
|
|
875
1442
|
const cap = option('--max-output-tokens');
|
|
876
1443
|
const workspace = option('--workspace-id');
|
|
877
|
-
|
|
1444
|
+
let route = providerRoute(
|
|
878
1445
|
provider,
|
|
879
1446
|
model,
|
|
880
1447
|
cap === undefined ? undefined : Number(cap),
|
|
881
1448
|
workspace,
|
|
882
1449
|
);
|
|
1450
|
+
if (route.provider !== 'salaam-proof') {
|
|
1451
|
+
const auth = await resolveProviderAuth(route);
|
|
1452
|
+
if (cap !== undefined && !providerHasOutputCap(route, auth))
|
|
1453
|
+
throw new Error(
|
|
1454
|
+
'This authentication cannot enforce --max-output-tokens; omit it and explicitly disable token budgeting in task plans',
|
|
1455
|
+
);
|
|
1456
|
+
route = bindProviderConfiguration(route, auth);
|
|
1457
|
+
}
|
|
883
1458
|
const effectSocket = option('--effect-socket');
|
|
884
1459
|
const opaque = args.includes('--effect-opaque');
|
|
885
1460
|
const codingRoot = option('--coding-root'),
|
|
886
|
-
codingSocket = option('--coding-socket')
|
|
1461
|
+
codingSocket = option('--coding-socket'),
|
|
1462
|
+
harnessConfig = option('--harness-config'),
|
|
1463
|
+
a2aConfig = option('--a2a-config'),
|
|
1464
|
+
a2aListenConfig = option('--a2a-listen-config');
|
|
887
1465
|
if (foreground) {
|
|
888
1466
|
const service = await SalaamService.open({
|
|
889
1467
|
stateDir: state,
|
|
890
1468
|
providerRoute: route,
|
|
891
1469
|
codingRoot: codingRoot ? resolve(codingRoot) : undefined,
|
|
892
1470
|
codingSocket: codingSocket ? resolve(codingSocket) : undefined,
|
|
1471
|
+
harnessConfig: harnessConfig ? resolve(harnessConfig) : undefined,
|
|
1472
|
+
a2aConfig: a2aConfig ? resolve(a2aConfig) : undefined,
|
|
1473
|
+
a2aListenConfig: a2aListenConfig ? resolve(a2aListenConfig) : undefined,
|
|
893
1474
|
effectConnector: effectSocket
|
|
894
1475
|
? new SocketEffectConnector(resolve(effectSocket), { opaque })
|
|
895
1476
|
: undefined,
|
|
896
1477
|
});
|
|
1478
|
+
const warning = service.supervisor.providerCapabilities().warning;
|
|
1479
|
+
if (warning) console.error(warning);
|
|
897
1480
|
process.stdout.write(JSON.stringify({ socket, service: 'started' }) + '\n');
|
|
898
1481
|
for (const signal of ['SIGINT', 'SIGTERM'] as const)
|
|
899
1482
|
process.once(signal, () => {
|
|
@@ -924,7 +1507,6 @@ async function main(): Promise<void> {
|
|
|
924
1507
|
console.log('Salaam service already running');
|
|
925
1508
|
return;
|
|
926
1509
|
}
|
|
927
|
-
if (route.provider === 'anthropic') loadApiKey();
|
|
928
1510
|
mkdirSync(state, { recursive: true, mode: 0o700 });
|
|
929
1511
|
const fd = openSync(join(state, 'service.log'), 'a', 0o600);
|
|
930
1512
|
const child = spawn(
|
|
@@ -945,6 +1527,9 @@ async function main(): Promise<void> {
|
|
|
945
1527
|
...(opaque ? ['--effect-opaque'] : []),
|
|
946
1528
|
...(codingRoot ? ['--coding-root', resolve(codingRoot)] : []),
|
|
947
1529
|
...(codingSocket ? ['--coding-socket', resolve(codingSocket)] : []),
|
|
1530
|
+
...(harnessConfig ? ['--harness-config', resolve(harnessConfig)] : []),
|
|
1531
|
+
...(a2aConfig ? ['--a2a-config', resolve(a2aConfig)] : []),
|
|
1532
|
+
...(a2aListenConfig ? ['--a2a-listen-config', resolve(a2aListenConfig)] : []),
|
|
948
1533
|
],
|
|
949
1534
|
{ detached: true, stdio: ['ignore', fd, fd] },
|
|
950
1535
|
);
|
|
@@ -952,7 +1537,12 @@ async function main(): Promise<void> {
|
|
|
952
1537
|
child.unref();
|
|
953
1538
|
for (let i = 0; i < 150; i++) {
|
|
954
1539
|
try {
|
|
955
|
-
await call(
|
|
1540
|
+
const health = await call<{ provider_capabilities?: { warning: string | null } }>(
|
|
1541
|
+
socket,
|
|
1542
|
+
'/v1/health',
|
|
1543
|
+
);
|
|
1544
|
+
if (health.provider_capabilities?.warning)
|
|
1545
|
+
console.error(health.provider_capabilities.warning);
|
|
956
1546
|
console.log(JSON.stringify({ socket, service: 'started' }));
|
|
957
1547
|
return;
|
|
958
1548
|
} catch {
|