@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/dist/src/cli.js
CHANGED
|
@@ -1,23 +1,28 @@
|
|
|
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 { mkdirSync, openSync, closeSync, readFileSync } from 'node:fs';
|
|
9
|
+
import { mkdirSync, openSync, closeSync, readFileSync, realpathSync, writeFileSync, fsyncSync, } from 'node:fs';
|
|
9
10
|
import { VERSION } from './config.js';
|
|
10
11
|
import { cliHelp, hasHelpFlag } from './cli-help.js';
|
|
11
12
|
import { SalaamService } from './service.js';
|
|
12
13
|
import { call } from './transport.js';
|
|
13
14
|
import { attach, acquireControl, Outbox } from './attach.js';
|
|
14
|
-
import { providerRoute } from './provider-config.js';
|
|
15
|
-
import {
|
|
15
|
+
import { providerRoute, PI_PROVIDERS, getProvider, providerHasOutputCap, } from './provider-config.js';
|
|
16
|
+
import { readSecret, validateApiKey } from './credentials.js';
|
|
17
|
+
import { authDirectory, ProviderCredentialStore, providerModels, loginProvider, resolveProviderAuth, } from './provider-auth.js';
|
|
18
|
+
import { bindProviderConfiguration } from './provider-model.js';
|
|
19
|
+
import { createInterface } from 'node:readline/promises';
|
|
16
20
|
import { canonical } from './protocol.js';
|
|
17
21
|
import { SocketEffectConnector } from './effect-connector.js';
|
|
18
22
|
import { proveExecutionBoundary } from './execution-proof.js';
|
|
19
23
|
import { BoundaryBroker, engineRequest } from './execution-boundary.js';
|
|
20
24
|
import { workspaceDiff } from './workspace-diff.js';
|
|
25
|
+
import { probeHarness, HARNESS_TARGETS, } from './harness-probe.js';
|
|
21
26
|
async function main() {
|
|
22
27
|
const args = process.argv.slice(2);
|
|
23
28
|
if (args[0] === '--version' || args[0] === '-V' || args[0] === 'version') {
|
|
@@ -54,6 +59,334 @@ async function main() {
|
|
|
54
59
|
args.splice(args.indexOf('--foreground'), 1);
|
|
55
60
|
const socket = join(state, 'operator.sock');
|
|
56
61
|
const [group, verb, id] = args;
|
|
62
|
+
if (group === 'a2a' && verb === 'credential') {
|
|
63
|
+
const file = option('--file');
|
|
64
|
+
if (!id || !file)
|
|
65
|
+
throw Error('Supply PEER_ID and --file NEW_OWNER_ONLY_FILE');
|
|
66
|
+
const { a2aIdentity } = await import('./a2a-peer.js');
|
|
67
|
+
a2aIdentity(id);
|
|
68
|
+
const path = resolve(file), value = { peer: id, token: randomBytes(32).toString('base64url') };
|
|
69
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
70
|
+
const fd = openSync(path, 'wx', 0o600);
|
|
71
|
+
try {
|
|
72
|
+
writeFileSync(fd, JSON.stringify(value) + '\n');
|
|
73
|
+
fsyncSync(fd);
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
closeSync(fd);
|
|
77
|
+
}
|
|
78
|
+
console.log(JSON.stringify({ peer: id, file: realpathSync(path), credential_digest: digest(value) }, null, 2));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (group === 'a2a' && verb === 'discover') {
|
|
82
|
+
const peer = option('--peer'), endpoint = option('--endpoint'), card_url = option('--card-url'), credential = option('--credential-file'), addresses = option('--addresses');
|
|
83
|
+
if (!peer || !endpoint || !card_url || !credential || !addresses)
|
|
84
|
+
throw Error('Supply --peer, --endpoint, --card-url, --credential-file and --addresses');
|
|
85
|
+
const { privateJSON, validatePeer, A2AHttp } = await import('./a2a-peer.js');
|
|
86
|
+
const credential_file = realpathSync(resolve(credential)), value = privateJSON(credential_file);
|
|
87
|
+
const pin = validatePeer({
|
|
88
|
+
id: peer,
|
|
89
|
+
endpoint,
|
|
90
|
+
card_url,
|
|
91
|
+
card_digest: digest(null),
|
|
92
|
+
tenant: option('--tenant') ?? '',
|
|
93
|
+
skill: 'pending-review',
|
|
94
|
+
addresses: addresses.split(','),
|
|
95
|
+
local: args.includes('--local'),
|
|
96
|
+
credential_file,
|
|
97
|
+
credential_digest: digest(value),
|
|
98
|
+
files: [],
|
|
99
|
+
retention: 'unknown',
|
|
100
|
+
cancel: false,
|
|
101
|
+
streaming: false,
|
|
102
|
+
});
|
|
103
|
+
console.log(JSON.stringify(await new A2AHttp(pin).request(card_url), null, 2));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (group === 'a2a' && verb === 'pin') {
|
|
107
|
+
const peer = option('--peer'), endpoint = option('--endpoint'), cardFile = option('--card-file'), credential = option('--credential-file'), addresses = option('--addresses'), skill = option('--skill');
|
|
108
|
+
if (!peer || !endpoint || !cardFile || !credential || !addresses || !skill)
|
|
109
|
+
throw Error('Supply --peer, --endpoint, --card-file, --credential-file, --addresses and --skill');
|
|
110
|
+
const { privateJSON, validatePeer, A2AHttp } = await import('./a2a-peer.js');
|
|
111
|
+
const card = JSON.parse(readFileSync(resolve(cardFile), 'utf8')), credential_file = realpathSync(resolve(credential)), value = privateJSON(credential_file), card_url = option('--card-url');
|
|
112
|
+
if (!card_url)
|
|
113
|
+
throw Error('Supply the exact --card-url that was reviewed');
|
|
114
|
+
const pin = validatePeer({
|
|
115
|
+
id: peer,
|
|
116
|
+
endpoint,
|
|
117
|
+
card_url,
|
|
118
|
+
card_digest: digest(card),
|
|
119
|
+
tenant: option('--tenant') ?? '',
|
|
120
|
+
skill,
|
|
121
|
+
addresses: addresses.split(','),
|
|
122
|
+
local: args.includes('--local'),
|
|
123
|
+
credential_file,
|
|
124
|
+
credential_digest: digest(value),
|
|
125
|
+
files: option('--files')?.split(',').filter(Boolean) ?? [],
|
|
126
|
+
retention: option('--retention') ?? 'unknown',
|
|
127
|
+
cancel: args.includes('--supports-cancel'),
|
|
128
|
+
streaming: args.includes('--supports-streaming'),
|
|
129
|
+
});
|
|
130
|
+
new A2AHttp(pin).assertCard(card);
|
|
131
|
+
console.log(JSON.stringify(pin, null, 2));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (group === 'a2a' && verb === 'grant') {
|
|
135
|
+
const assignment = option('--assignment'), peer = option('--peer'), credential = option('--credential-file');
|
|
136
|
+
if (!assignment || !peer || !credential)
|
|
137
|
+
throw Error('Supply --assignment, --peer and --credential-file');
|
|
138
|
+
const { privateJSON } = await import('./a2a-peer.js');
|
|
139
|
+
const credential_file = realpathSync(resolve(credential)), value = object(privateJSON(credential_file));
|
|
140
|
+
if (value.peer !== peer ||
|
|
141
|
+
Object.keys(value).sort().join(',') !== 'peer,token' ||
|
|
142
|
+
typeof value.token !== 'string' ||
|
|
143
|
+
!/^[A-Za-z0-9._~-]{32,512}$/.test(value.token))
|
|
144
|
+
throw Error('Use a dedicated peer credential JSON {peer,token}');
|
|
145
|
+
const peerClient = option('--peer-client');
|
|
146
|
+
if (peerClient && peerClient !== 'openclaw-2026.10.5')
|
|
147
|
+
throw Error('Only the explicitly pinned OpenClaw client adapter is available');
|
|
148
|
+
const binding = await call(socket, `/v1/a2a/grants/${encodeURIComponent(assignment)}`);
|
|
149
|
+
console.log(JSON.stringify({
|
|
150
|
+
peer,
|
|
151
|
+
tenant: option('--tenant') ?? '',
|
|
152
|
+
skill: option('--skill') ?? 'review',
|
|
153
|
+
assignment_id: assignment,
|
|
154
|
+
binding_digest: digest(binding),
|
|
155
|
+
credential_file,
|
|
156
|
+
credential_digest: digest(value),
|
|
157
|
+
...(peerClient ? { client: peerClient } : {}),
|
|
158
|
+
}, null, 2));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (group === 'a2a' && verb === 'peers') {
|
|
162
|
+
console.log(JSON.stringify((await call(socket, '/v1/harnesses')).filter((r) => r.kind === 'a2a'), null, 2));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (group === 'harness' && verb === 'probe') {
|
|
166
|
+
const path = option('--pin');
|
|
167
|
+
if (!path)
|
|
168
|
+
throw new Error('Supply an operator-reviewed --pin FILE');
|
|
169
|
+
const pin = JSON.parse(readFileSync(resolve(path), 'utf8'));
|
|
170
|
+
console.log(JSON.stringify(await probeHarness(pin), null, 2));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (group === 'harness' && verb === 'prepare') {
|
|
174
|
+
const path = option('--runtime-root'), arch = option('--arch');
|
|
175
|
+
if (!path || !arch)
|
|
176
|
+
throw Error('Supply a new --runtime-root and execution-host --arch');
|
|
177
|
+
const { prepareHarnessRuntime } = await import('./harness-prepare.js');
|
|
178
|
+
console.log(JSON.stringify(await prepareHarnessRuntime(path, arch), null, 2));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (group === 'harness' && verb === 'config') {
|
|
182
|
+
const target = option('--target'), path = option('--runtime-root');
|
|
183
|
+
if (!target || !Object.hasOwn(HARNESS_TARGETS, target) || !path)
|
|
184
|
+
throw Error('Supply a named --target and dedicated --runtime-root');
|
|
185
|
+
const runtime_root = realpathSync(resolve(path));
|
|
186
|
+
const { assertHarnessArtifactRoot, harnessRuntimeDigest } = await import('./harness-backend.js');
|
|
187
|
+
assertHarnessArtifactRoot(runtime_root);
|
|
188
|
+
console.log(JSON.stringify({
|
|
189
|
+
schema: 1,
|
|
190
|
+
runtimes: [
|
|
191
|
+
{
|
|
192
|
+
id: option('--id') ?? target,
|
|
193
|
+
target,
|
|
194
|
+
version: HARNESS_TARGETS[target].version,
|
|
195
|
+
runtime_root,
|
|
196
|
+
runtime_digest: await harnessRuntimeDigest(runtime_root),
|
|
197
|
+
model: 'claude-sonnet-4-6',
|
|
198
|
+
output: 4096,
|
|
199
|
+
requests: 12,
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
}, null, 2));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (['harness', 'a2a'].includes(group ?? '') &&
|
|
206
|
+
['list', 'inspect', 'reconcile'].includes(verb ?? '')) {
|
|
207
|
+
if (verb === 'list') {
|
|
208
|
+
console.log(JSON.stringify(await call(socket, '/v1/harnesses'), null, 2));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (!id)
|
|
212
|
+
throw Error('Supply an external CHILD_ID');
|
|
213
|
+
const current = await call(socket, `/v1/harnesses/${id}`);
|
|
214
|
+
if (verb === 'inspect') {
|
|
215
|
+
console.log(JSON.stringify(current, null, 2));
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const run_id = option('--run-id');
|
|
219
|
+
if (!run_id)
|
|
220
|
+
throw Error('Supply the exact --run-id from harness inspect');
|
|
221
|
+
const client_id = randomUUID(), owner = current.child.parent_session, lease = await acquireControl(socket, owner, client_id, args.includes('--takeover'));
|
|
222
|
+
try {
|
|
223
|
+
console.log(JSON.stringify(await call(socket, `/v1/harnesses/${id}/reconcile`, {
|
|
224
|
+
client_id,
|
|
225
|
+
token: lease.token,
|
|
226
|
+
run_id,
|
|
227
|
+
request_id: option('--request-id') ?? randomUUID(),
|
|
228
|
+
}), null, 2));
|
|
229
|
+
}
|
|
230
|
+
finally {
|
|
231
|
+
await call(socket, `/v1/sessions/${owner}/control`, {
|
|
232
|
+
request_id: randomUUID(),
|
|
233
|
+
client_id,
|
|
234
|
+
token: lease.token,
|
|
235
|
+
action: 'release',
|
|
236
|
+
expected_epoch: lease.epoch,
|
|
237
|
+
}).catch(() => { });
|
|
238
|
+
}
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (group === 'workers') {
|
|
242
|
+
if (!id)
|
|
243
|
+
throw new Error('Supply a child or integration receipt ID');
|
|
244
|
+
if (verb === 'proposal' || verb === 'integration') {
|
|
245
|
+
const result = await call(socket, verb === 'proposal'
|
|
246
|
+
? `/v1/workers/${id}/proposal?offset=${Number(option('--offset') ?? 0)}`
|
|
247
|
+
: `/v1/integrations/${id}`);
|
|
248
|
+
if (verb === 'proposal' && !args.includes('--json')) {
|
|
249
|
+
const { changes, diff, ...metadata } = result;
|
|
250
|
+
console.log(JSON.stringify({
|
|
251
|
+
...metadata,
|
|
252
|
+
files: changes.map((c) => ({
|
|
253
|
+
path: c.path,
|
|
254
|
+
before_hash: digest(c.before),
|
|
255
|
+
after_hash: digest(c.after),
|
|
256
|
+
})),
|
|
257
|
+
}, null, 2));
|
|
258
|
+
for (const patch of diff)
|
|
259
|
+
console.log(patch);
|
|
260
|
+
}
|
|
261
|
+
else
|
|
262
|
+
console.log(JSON.stringify(result, null, 2));
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (!['integrate', 'resolve'].includes(verb ?? ''))
|
|
266
|
+
throw new Error('Use workers proposal, integrate, integration or resolve');
|
|
267
|
+
const owner = verb === 'integrate'
|
|
268
|
+
? (await call(socket, `/v1/children/${id}`)).parent_session
|
|
269
|
+
: (await call(socket, `/v1/integrations/${id}`)).parent_session;
|
|
270
|
+
const revision = option('--revision'), workspace_digest = option('--root-digest'), patch_digest = option('--patch-digest');
|
|
271
|
+
if (revision === undefined || !workspace_digest || (verb === 'integrate' && !patch_digest))
|
|
272
|
+
throw new Error('Supply reviewed --revision N, --root-digest SHA256 and --patch-digest SHA256 for integration');
|
|
273
|
+
const client_id = randomUUID(), lease = await acquireControl(socket, owner, client_id, args.includes('--takeover'));
|
|
274
|
+
try {
|
|
275
|
+
const result = await call(socket, verb === 'integrate' ? `/v1/workers/${id}/integrate` : `/v1/integrations/${id}/resolve`, {
|
|
276
|
+
client_id,
|
|
277
|
+
token: lease.token,
|
|
278
|
+
request_id: option('--request-id') ?? randomUUID(),
|
|
279
|
+
workspace_revision: Number(revision),
|
|
280
|
+
workspace_digest,
|
|
281
|
+
patch_digest,
|
|
282
|
+
});
|
|
283
|
+
console.log(JSON.stringify(result, null, 2));
|
|
284
|
+
}
|
|
285
|
+
finally {
|
|
286
|
+
await call(socket, `/v1/sessions/${owner}/control`, {
|
|
287
|
+
request_id: randomUUID(),
|
|
288
|
+
client_id,
|
|
289
|
+
token: lease.token,
|
|
290
|
+
action: 'release',
|
|
291
|
+
expected_epoch: lease.epoch,
|
|
292
|
+
}).catch(() => { });
|
|
293
|
+
}
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (group === 'team') {
|
|
297
|
+
if (!id)
|
|
298
|
+
throw new Error('Supply TASK_ID');
|
|
299
|
+
if (verb === 'show') {
|
|
300
|
+
console.log(JSON.stringify(await call(socket, `/v1/tasks/${id}/team`), null, 2));
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (!['assign', 'run', 'reject', 'send', 'receive', 'ack'].includes(verb ?? ''))
|
|
304
|
+
throw new Error('Use team show, assign, run, send, receive or ack');
|
|
305
|
+
const task = await call(socket, `/v1/tasks/${id}`);
|
|
306
|
+
const client_id = randomUUID(), lease = await acquireControl(socket, task.session_id, client_id, args.includes('--takeover'));
|
|
307
|
+
try {
|
|
308
|
+
const assignment = option('--assignment');
|
|
309
|
+
if (['run', 'reject', 'receive', 'ack'].includes(verb) && !assignment)
|
|
310
|
+
throw new Error('Supply --assignment ID');
|
|
311
|
+
const revision = option('--revision'), generation = option('--generation');
|
|
312
|
+
if (['assign', 'run', 'reject'].includes(verb) && revision === undefined)
|
|
313
|
+
throw new Error('Supply the reviewed --revision N');
|
|
314
|
+
if (['run', 'reject'].includes(verb) && generation === undefined)
|
|
315
|
+
throw new Error('Supply the reviewed --generation N');
|
|
316
|
+
const path = assignment ? `${assignment}/${verb}` : verb;
|
|
317
|
+
const result = await call(socket, `/v1/tasks/${id}/team/${path}`, {
|
|
318
|
+
client_id,
|
|
319
|
+
token: lease.token,
|
|
320
|
+
request_id: option('--request-id') ?? randomUUID(),
|
|
321
|
+
expected_revision: revision === undefined ? undefined : Number(revision),
|
|
322
|
+
generation: generation === undefined ? undefined : Number(generation),
|
|
323
|
+
...(verb === 'assign'
|
|
324
|
+
? {
|
|
325
|
+
step_id: option('--step'),
|
|
326
|
+
profile_id: option('--profile'),
|
|
327
|
+
...(() => {
|
|
328
|
+
const runtime = option('--runtime');
|
|
329
|
+
return runtime
|
|
330
|
+
? { runtime, invocations: Number(option('--invocations')) }
|
|
331
|
+
: { requests: Number(option('--requests')) };
|
|
332
|
+
})(),
|
|
333
|
+
}
|
|
334
|
+
: {}),
|
|
335
|
+
...(verb === 'run'
|
|
336
|
+
? { instruction: option('--instruction'), continues_attempt_id: option('--continues') }
|
|
337
|
+
: {}),
|
|
338
|
+
...(verb === 'send' ? { recipient: option('--to'), text: option('--text') } : {}),
|
|
339
|
+
...(verb === 'ack' ? { message_id: option('--message') } : {}),
|
|
340
|
+
...(verb === 'reject' ? { reason: option('--reason') } : {}),
|
|
341
|
+
});
|
|
342
|
+
console.log(JSON.stringify(result, null, 2));
|
|
343
|
+
}
|
|
344
|
+
finally {
|
|
345
|
+
await call(socket, `/v1/sessions/${task.session_id}/control`, {
|
|
346
|
+
request_id: randomUUID(),
|
|
347
|
+
client_id,
|
|
348
|
+
token: lease.token,
|
|
349
|
+
action: 'release',
|
|
350
|
+
expected_epoch: lease.epoch,
|
|
351
|
+
}).catch(() => { });
|
|
352
|
+
}
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
if (group === 'agents') {
|
|
356
|
+
const session = option('--session');
|
|
357
|
+
if (!session)
|
|
358
|
+
throw new Error('Supply --session SESSION_ID');
|
|
359
|
+
if (verb === 'list' || verb === 'inspect') {
|
|
360
|
+
console.log(JSON.stringify(await call(socket, `/v1/sessions/${session}/agents${verb === 'inspect' ? '?profile=' + encodeURIComponent(id ?? '') : ''}`), null, 2));
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
if (!id || !['admit', 'revoke'].includes(verb ?? ''))
|
|
364
|
+
throw new Error('Use agents list, inspect, admit or revoke');
|
|
365
|
+
const client_id = randomUUID(), lease = await acquireControl(socket, session, client_id, args.includes('--takeover'));
|
|
366
|
+
try {
|
|
367
|
+
const expected_digest = option('--digest');
|
|
368
|
+
if (verb === 'admit' && !expected_digest)
|
|
369
|
+
throw new Error('Review agents inspect and supply --digest SHA256');
|
|
370
|
+
const fact_ids = option('--facts')?.split(',').filter(Boolean) ?? [];
|
|
371
|
+
const result = await call(socket, `/v1/sessions/${session}/agents/${verb}`, {
|
|
372
|
+
request_id: option('--request-id') ?? randomUUID(),
|
|
373
|
+
client_id,
|
|
374
|
+
token: lease.token,
|
|
375
|
+
...(verb === 'admit' ? { profile_id: id, expected_digest, fact_ids } : { grant_id: id }),
|
|
376
|
+
});
|
|
377
|
+
console.log(JSON.stringify(result, null, 2));
|
|
378
|
+
}
|
|
379
|
+
finally {
|
|
380
|
+
await call(socket, `/v1/sessions/${session}/control`, {
|
|
381
|
+
request_id: randomUUID(),
|
|
382
|
+
client_id,
|
|
383
|
+
token: lease.token,
|
|
384
|
+
action: 'release',
|
|
385
|
+
expected_epoch: lease.epoch,
|
|
386
|
+
}).catch(() => { });
|
|
387
|
+
}
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
57
390
|
if (group === 'host') {
|
|
58
391
|
const { planHost, readHost, hostUnit, runHost } = await import('./host.js');
|
|
59
392
|
if (verb === 'plan') {
|
|
@@ -262,11 +595,127 @@ async function main() {
|
|
|
262
595
|
await acp(state, { coding: args.includes('--coding') });
|
|
263
596
|
return;
|
|
264
597
|
}
|
|
265
|
-
if (group === '
|
|
266
|
-
|
|
267
|
-
|
|
598
|
+
if (group === 'providers' && verb === 'list') {
|
|
599
|
+
console.log(JSON.stringify(PI_PROVIDERS.map((p) => ({
|
|
600
|
+
id: p.id,
|
|
601
|
+
name: p.name,
|
|
602
|
+
chat_models: p.getModels().length,
|
|
603
|
+
apis: [...new Set(p.getModels().map((m) => m.api))],
|
|
604
|
+
auth: [p.auth.apiKey ? 'api_key' : null, p.auth.oauth ? 'oauth' : null].filter(Boolean),
|
|
605
|
+
limitation: !p.getModels().length
|
|
606
|
+
? 'Classifier only; no chat route'
|
|
607
|
+
: p.id === 'openai-codex'
|
|
608
|
+
? 'No provider-enforced output cap; tasks must explicitly disable token budgeting'
|
|
609
|
+
: null,
|
|
610
|
+
})), null, 2));
|
|
268
611
|
return;
|
|
269
612
|
}
|
|
613
|
+
if (group === 'models' && verb === 'list') {
|
|
614
|
+
const providers = id ? [getProvider(id)] : PI_PROVIDERS;
|
|
615
|
+
if (providers.some((p) => !p))
|
|
616
|
+
throw new Error('Unknown Pi provider');
|
|
617
|
+
console.log(JSON.stringify(providers.flatMap((p) => p.getModels().map((m) => ({
|
|
618
|
+
provider: p.id,
|
|
619
|
+
id: m.id,
|
|
620
|
+
name: m.name,
|
|
621
|
+
api: m.api,
|
|
622
|
+
context_window: m.contextWindow,
|
|
623
|
+
max_output_tokens: m.maxTokens,
|
|
624
|
+
}))), null, 2));
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
if (group === 'auth') {
|
|
628
|
+
const store = new ProviderCredentialStore();
|
|
629
|
+
if (verb === 'status') {
|
|
630
|
+
const models = providerModels(store), entries = id ? [getProvider(id)] : PI_PROVIDERS;
|
|
631
|
+
if (entries.some((p) => !p))
|
|
632
|
+
throw new Error('Unknown Pi provider');
|
|
633
|
+
const statuses = [];
|
|
634
|
+
for (const p of entries) {
|
|
635
|
+
try {
|
|
636
|
+
const auth = await models.checkAuth(p.id);
|
|
637
|
+
statuses.push({
|
|
638
|
+
provider: p.id,
|
|
639
|
+
configured: !!auth,
|
|
640
|
+
type: auth?.type ?? null,
|
|
641
|
+
source: auth?.source ?? null,
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
catch {
|
|
645
|
+
statuses.push({ provider: p.id, configured: false, error: 'Credential check failed' });
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
console.log(JSON.stringify(statuses, null, 2));
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
if (!id || !getProvider(id))
|
|
652
|
+
throw new Error('Choose a provider from salaam providers list');
|
|
653
|
+
if (verb === 'logout') {
|
|
654
|
+
await store.delete(id);
|
|
655
|
+
console.log('Stored credential removed for ' + id + '. Environment credentials remain external.');
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
if (verb === 'set') {
|
|
659
|
+
if (!getProvider(id).auth.apiKey)
|
|
660
|
+
throw new Error('This provider needs salaam auth login ' + id + ' --oauth');
|
|
661
|
+
const key = (await readSecret(id + ' API key (hidden): ')).trim();
|
|
662
|
+
if (!key ||
|
|
663
|
+
key.length > 4096 ||
|
|
664
|
+
/[^\x21-\x7e]/.test(key) ||
|
|
665
|
+
(id === 'anthropic' && key.includes('sk-ant-oat')))
|
|
666
|
+
throw new Error('Supply a provider API key, not an OAuth token');
|
|
667
|
+
if (id === 'anthropic')
|
|
668
|
+
validateApiKey(key);
|
|
669
|
+
await store.modify(id, async () => ({ type: 'api_key', key }));
|
|
670
|
+
console.log('API key saved privately for ' + id + ' under ' + authDirectory() + ' (mode 600).');
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
if (verb === 'login') {
|
|
674
|
+
if (!process.stdin.isTTY)
|
|
675
|
+
throw new Error('Interactive login needs a terminal; use auth set PROVIDER for a piped API key');
|
|
676
|
+
const provider = getProvider(id);
|
|
677
|
+
const type = args.includes('--oauth') || !provider.auth.apiKey ? 'oauth' : 'api_key';
|
|
678
|
+
const interaction = {
|
|
679
|
+
async prompt(prompt) {
|
|
680
|
+
if (prompt.type === 'secret')
|
|
681
|
+
return readSecret(prompt.message + ' (hidden): ', prompt.signal);
|
|
682
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
683
|
+
try {
|
|
684
|
+
const choices = prompt.type === 'select'
|
|
685
|
+
? '\n' + prompt.options.map((v, i) => `${i + 1}. ${v.label} (${v.id})`).join('\n')
|
|
686
|
+
: '';
|
|
687
|
+
const answer = await rl.question(prompt.message + choices + '\n> ', {
|
|
688
|
+
signal: prompt.signal,
|
|
689
|
+
});
|
|
690
|
+
return prompt.type === 'select' && /^\d+$/.test(answer)
|
|
691
|
+
? (prompt.options[Number(answer) - 1]?.id ?? answer)
|
|
692
|
+
: answer;
|
|
693
|
+
}
|
|
694
|
+
finally {
|
|
695
|
+
rl.close();
|
|
696
|
+
}
|
|
697
|
+
},
|
|
698
|
+
notify(event) {
|
|
699
|
+
if (event.type === 'auth_url')
|
|
700
|
+
console.error('Open in your browser: ' +
|
|
701
|
+
event.url +
|
|
702
|
+
(event.instructions ? '\n' + event.instructions : ''));
|
|
703
|
+
else if (event.type === 'device_code')
|
|
704
|
+
console.error('Open ' + event.verificationUri + ' and enter ' + event.userCode);
|
|
705
|
+
else
|
|
706
|
+
console.error(event.message);
|
|
707
|
+
},
|
|
708
|
+
};
|
|
709
|
+
await loginProvider(id, type, interaction);
|
|
710
|
+
console.log('Signed in to ' +
|
|
711
|
+
id +
|
|
712
|
+
' with ' +
|
|
713
|
+
type +
|
|
714
|
+
'. Restart the service to use a different provider route.');
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
throw new Error('Run salaam auth --help');
|
|
718
|
+
}
|
|
270
719
|
if (group === 'schedules' && verb) {
|
|
271
720
|
if (verb === 'list' || (id && ['inspect', 'occurrences'].includes(verb))) {
|
|
272
721
|
console.log(JSON.stringify(await call(socket, verb === 'list'
|
|
@@ -705,20 +1154,32 @@ async function main() {
|
|
|
705
1154
|
const model = option('--model');
|
|
706
1155
|
const cap = option('--max-output-tokens');
|
|
707
1156
|
const workspace = option('--workspace-id');
|
|
708
|
-
|
|
1157
|
+
let route = providerRoute(provider, model, cap === undefined ? undefined : Number(cap), workspace);
|
|
1158
|
+
if (route.provider !== 'salaam-proof') {
|
|
1159
|
+
const auth = await resolveProviderAuth(route);
|
|
1160
|
+
if (cap !== undefined && !providerHasOutputCap(route, auth))
|
|
1161
|
+
throw new Error('This authentication cannot enforce --max-output-tokens; omit it and explicitly disable token budgeting in task plans');
|
|
1162
|
+
route = bindProviderConfiguration(route, auth);
|
|
1163
|
+
}
|
|
709
1164
|
const effectSocket = option('--effect-socket');
|
|
710
1165
|
const opaque = args.includes('--effect-opaque');
|
|
711
|
-
const codingRoot = option('--coding-root'), codingSocket = option('--coding-socket');
|
|
1166
|
+
const codingRoot = option('--coding-root'), codingSocket = option('--coding-socket'), harnessConfig = option('--harness-config'), a2aConfig = option('--a2a-config'), a2aListenConfig = option('--a2a-listen-config');
|
|
712
1167
|
if (foreground) {
|
|
713
1168
|
const service = await SalaamService.open({
|
|
714
1169
|
stateDir: state,
|
|
715
1170
|
providerRoute: route,
|
|
716
1171
|
codingRoot: codingRoot ? resolve(codingRoot) : undefined,
|
|
717
1172
|
codingSocket: codingSocket ? resolve(codingSocket) : undefined,
|
|
1173
|
+
harnessConfig: harnessConfig ? resolve(harnessConfig) : undefined,
|
|
1174
|
+
a2aConfig: a2aConfig ? resolve(a2aConfig) : undefined,
|
|
1175
|
+
a2aListenConfig: a2aListenConfig ? resolve(a2aListenConfig) : undefined,
|
|
718
1176
|
effectConnector: effectSocket
|
|
719
1177
|
? new SocketEffectConnector(resolve(effectSocket), { opaque })
|
|
720
1178
|
: undefined,
|
|
721
1179
|
});
|
|
1180
|
+
const warning = service.supervisor.providerCapabilities().warning;
|
|
1181
|
+
if (warning)
|
|
1182
|
+
console.error(warning);
|
|
722
1183
|
process.stdout.write(JSON.stringify({ socket, service: 'started' }) + '\n');
|
|
723
1184
|
for (const signal of ['SIGINT', 'SIGTERM'])
|
|
724
1185
|
process.once(signal, () => {
|
|
@@ -744,8 +1205,6 @@ async function main() {
|
|
|
744
1205
|
console.log('Salaam service already running');
|
|
745
1206
|
return;
|
|
746
1207
|
}
|
|
747
|
-
if (route.provider === 'anthropic')
|
|
748
|
-
loadApiKey();
|
|
749
1208
|
mkdirSync(state, { recursive: true, mode: 0o700 });
|
|
750
1209
|
const fd = openSync(join(state, 'service.log'), 'a', 0o600);
|
|
751
1210
|
const child = spawn(process.execPath, [
|
|
@@ -764,12 +1223,17 @@ async function main() {
|
|
|
764
1223
|
...(opaque ? ['--effect-opaque'] : []),
|
|
765
1224
|
...(codingRoot ? ['--coding-root', resolve(codingRoot)] : []),
|
|
766
1225
|
...(codingSocket ? ['--coding-socket', resolve(codingSocket)] : []),
|
|
1226
|
+
...(harnessConfig ? ['--harness-config', resolve(harnessConfig)] : []),
|
|
1227
|
+
...(a2aConfig ? ['--a2a-config', resolve(a2aConfig)] : []),
|
|
1228
|
+
...(a2aListenConfig ? ['--a2a-listen-config', resolve(a2aListenConfig)] : []),
|
|
767
1229
|
], { detached: true, stdio: ['ignore', fd, fd] });
|
|
768
1230
|
closeSync(fd);
|
|
769
1231
|
child.unref();
|
|
770
1232
|
for (let i = 0; i < 150; i++) {
|
|
771
1233
|
try {
|
|
772
|
-
await call(socket, '/v1/health');
|
|
1234
|
+
const health = await call(socket, '/v1/health');
|
|
1235
|
+
if (health.provider_capabilities?.warning)
|
|
1236
|
+
console.error(health.provider_capabilities.warning);
|
|
773
1237
|
console.log(JSON.stringify({ socket, service: 'started' }));
|
|
774
1238
|
return;
|
|
775
1239
|
}
|
package/dist/src/config.js
CHANGED
|
@@ -2,6 +2,7 @@ import { readFileSync, existsSync } from 'node:fs';
|
|
|
2
2
|
import { dirname, join, resolve } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { digest, textDigest } from './protocol.js';
|
|
5
|
+
import { PI_PROVIDERS } from './provider-config.js';
|
|
5
6
|
import { packageDigest } from './package-digest.js';
|
|
6
7
|
let root = dirname(fileURLToPath(import.meta.url));
|
|
7
8
|
while (!existsSync(join(root, 'pi-pin.json'))) {
|
|
@@ -16,7 +17,7 @@ export const PI_ROOT = resolve(dirname(fileURLToPath(import.meta.resolve('@earen
|
|
|
16
17
|
export const PI_CLI = join(PI_ROOT, 'dist/bundle/cli.js');
|
|
17
18
|
export const HOUSE_EXTENSION = join(ROOT, 'dist/src/house-extension.js');
|
|
18
19
|
export const POLICY = {
|
|
19
|
-
version:
|
|
20
|
+
version: 14,
|
|
20
21
|
tools: [
|
|
21
22
|
'salaam_probe',
|
|
22
23
|
'salaam_review',
|
|
@@ -27,6 +28,9 @@ export const POLICY = {
|
|
|
27
28
|
'salaam_history',
|
|
28
29
|
'salaam_files',
|
|
29
30
|
'salaam_command',
|
|
31
|
+
'salaam_repository',
|
|
32
|
+
'salaam_findings',
|
|
33
|
+
'salaam_team',
|
|
30
34
|
],
|
|
31
35
|
children: {
|
|
32
36
|
maxDepth: 1,
|
|
@@ -34,13 +38,21 @@ export const POLICY = {
|
|
|
34
38
|
parentRequestBudget: 16,
|
|
35
39
|
maxChildRequestBudget: 8,
|
|
36
40
|
authority: ['salaam_probe'],
|
|
41
|
+
worker_boundary: 'frozen-native-model-request-v1',
|
|
42
|
+
specialist_profiles: 'operator-admitted-snapshot-v1',
|
|
43
|
+
team_claims: 'task-step-generation-v1',
|
|
44
|
+
daemon_workers: 8,
|
|
45
|
+
messages: 'passive-durable-explicit-ack-v1',
|
|
46
|
+
writing: 'owned-draft-explicit-hash-bound-integration-v1',
|
|
47
|
+
external: 'operator-pinned-whole-runtime-explicit-invocation-v1',
|
|
48
|
+
a2a: 'pinned-peer-one-turn-analysis-explicit-invocation-v1',
|
|
37
49
|
},
|
|
38
50
|
providers: [
|
|
39
51
|
'salaam-proof/scripted',
|
|
40
|
-
'
|
|
41
|
-
'anthropic/claude-sonnet-4-6',
|
|
52
|
+
...PI_PROVIDERS.flatMap((provider) => provider.getModels().map((model) => provider.id + '/' + model.id)).sort(),
|
|
42
53
|
],
|
|
43
54
|
provider_retries: 0,
|
|
55
|
+
task_token_budget: 'capped-default-explicit-null-opt-out-v1',
|
|
44
56
|
compaction: { strategy: 'explicit-task-checkpoint-v1', automatic: false, keepRecentTokens: 512 },
|
|
45
57
|
external_effects: 'explicit-connector-and-operation-approval',
|
|
46
58
|
coding: {
|
|
@@ -72,9 +84,20 @@ export function bundleDigest() {
|
|
|
72
84
|
policy: POLICY_DIGEST,
|
|
73
85
|
lock: textDigest(readFileSync(lockPath, 'utf8')),
|
|
74
86
|
house: textDigest(readFileSync(HOUSE_EXTENSION, 'utf8')),
|
|
87
|
+
provider_usage: textDigest(readFileSync(join(ROOT, 'dist/src/provider-usage.js'), 'utf8')),
|
|
88
|
+
provider_model: textDigest(readFileSync(join(ROOT, 'dist/src/provider-model.js'), 'utf8')),
|
|
89
|
+
live_provider: textDigest(readFileSync(join(ROOT, 'dist/src/live-provider.js'), 'utf8')),
|
|
90
|
+
provider_payload: textDigest(readFileSync(join(ROOT, 'dist/src/provider-payload.js'), 'utf8')),
|
|
91
|
+
provider_transport: textDigest(readFileSync(join(ROOT, 'dist/src/provider-transport.js'), 'utf8')),
|
|
92
|
+
provider_auth: textDigest(readFileSync(join(ROOT, 'dist/src/provider-auth.js'), 'utf8')),
|
|
75
93
|
claude: textDigest(readFileSync(join(ROOT, 'dist/src/claude-provider.js'), 'utf8')),
|
|
76
94
|
provider_config: textDigest(readFileSync(join(ROOT, 'dist/src/provider-config.js'), 'utf8')),
|
|
77
95
|
protocol: textDigest(readFileSync(join(ROOT, 'dist/src/protocol.js'), 'utf8')),
|
|
96
|
+
worker_boundary: textDigest(readFileSync(join(ROOT, 'dist/src/worker-boundary.js'), 'utf8')),
|
|
97
|
+
agent_profiles: textDigest(readFileSync(join(ROOT, 'dist/src/agent-profiles.js'), 'utf8')),
|
|
98
|
+
team_work: textDigest(readFileSync(join(ROOT, 'dist/src/team-work.js'), 'utf8')),
|
|
99
|
+
worker_workspaces: textDigest(readFileSync(join(ROOT, 'dist/src/worker-workspaces.js'), 'utf8')),
|
|
100
|
+
repository_snapshots: textDigest(readFileSync(join(ROOT, 'dist/src/repository-snapshots.js'), 'utf8')),
|
|
78
101
|
transport: textDigest(readFileSync(join(ROOT, 'dist/src/transport.js'), 'utf8')),
|
|
79
102
|
});
|
|
80
103
|
}
|
package/dist/src/credentials.js
CHANGED
|
@@ -58,7 +58,7 @@ export function saveApiKey(value, path = credentialPath()) {
|
|
|
58
58
|
closeSync(directory);
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
-
export async function readSecret() {
|
|
61
|
+
export async function readSecret(label = 'Claude API key (hidden): ', signal) {
|
|
62
62
|
if (!process.stdin.isTTY) {
|
|
63
63
|
let input = '';
|
|
64
64
|
for await (const chunk of process.stdin) {
|
|
@@ -68,13 +68,14 @@ export async function readSecret() {
|
|
|
68
68
|
}
|
|
69
69
|
return input;
|
|
70
70
|
}
|
|
71
|
-
process.stderr.write(
|
|
71
|
+
process.stderr.write(label);
|
|
72
72
|
const stdin = process.stdin, wasRaw = stdin.isRaw;
|
|
73
73
|
stdin.setRawMode(true);
|
|
74
74
|
stdin.resume();
|
|
75
75
|
return new Promise((resolve, reject) => {
|
|
76
76
|
let key = '';
|
|
77
77
|
const done = (error) => {
|
|
78
|
+
signal?.removeEventListener('abort', aborted);
|
|
78
79
|
stdin.off('data', data);
|
|
79
80
|
stdin.setRawMode(wasRaw);
|
|
80
81
|
stdin.pause();
|
|
@@ -103,6 +104,10 @@ export async function readSecret() {
|
|
|
103
104
|
}
|
|
104
105
|
}
|
|
105
106
|
};
|
|
107
|
+
const aborted = () => done(new Error('Credential entry cancelled'));
|
|
108
|
+
signal?.addEventListener('abort', aborted, { once: true });
|
|
106
109
|
stdin.on('data', data);
|
|
110
|
+
if (signal?.aborted)
|
|
111
|
+
aborted();
|
|
107
112
|
});
|
|
108
113
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|