@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/service.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { Store } from './store.js';
|
|
|
14
14
|
import { acquireStateLock } from './state-lock.js';
|
|
15
15
|
import { Artifacts } from './artifacts.js';
|
|
16
16
|
import { Supervisor, type SupervisorOptions } from './supervisor.js';
|
|
17
|
-
import { canonical, integer, object, ServiceError, string } from './protocol.js';
|
|
17
|
+
import { canonical, digest, integer, object, ServiceError, string } from './protocol.js';
|
|
18
18
|
import { readJson } from './transport.js';
|
|
19
19
|
import { Dispatcher } from './dispatcher.js';
|
|
20
20
|
import { ManagedWork } from './managed.js';
|
|
@@ -25,8 +25,28 @@ import { engineRequest } from './execution-boundary.js';
|
|
|
25
25
|
import { DockerCodingExecutor, type ExecutionBackend } from './coding-executor.js';
|
|
26
26
|
import { Workspaces } from './workspaces.js';
|
|
27
27
|
import { CodingWork } from './coding.js';
|
|
28
|
+
import { AgentProfiles } from './agent-profiles.js';
|
|
29
|
+
import { RepositorySnapshots } from './repository-snapshots.js';
|
|
30
|
+
import { TeamWork } from './team-work.js';
|
|
31
|
+
import { WorkerWorkspaces } from './worker-workspaces.js';
|
|
32
|
+
import { ExternalWork } from './external-work.js';
|
|
33
|
+
import type { ExternalBackend } from './external-runtime.js';
|
|
34
|
+
import { DockerHarnessBackend } from './harness-backend.js';
|
|
35
|
+
import { A2AInbound } from './a2a-inbound.js';
|
|
36
|
+
import { A2ABackend, ExternalBackends } from './a2a-backend.js';
|
|
28
37
|
|
|
29
38
|
export interface ServiceOptions extends Omit<SupervisorOptions, 'stateDir' | 'bridgeSocket'> {
|
|
39
|
+
externalBackend?: ExternalBackend;
|
|
40
|
+
harnessConfig?: string;
|
|
41
|
+
a2aConfig?: string;
|
|
42
|
+
a2aListenConfig?: string;
|
|
43
|
+
/** Trusted host admission, installed before any startup/recovery dispatch; throws deny a request. */
|
|
44
|
+
admitModelRequest?: (
|
|
45
|
+
session: string,
|
|
46
|
+
attempt: string | undefined,
|
|
47
|
+
input: number,
|
|
48
|
+
output: number,
|
|
49
|
+
) => void;
|
|
30
50
|
stateDir: string;
|
|
31
51
|
effectConnector?: EffectConnector;
|
|
32
52
|
approvalTtlMs?: number;
|
|
@@ -36,6 +56,8 @@ export interface ServiceOptions extends Omit<SupervisorOptions, 'stateDir' | 'br
|
|
|
36
56
|
expectedExecution?: { profile_digest: string; engine_id: string };
|
|
37
57
|
}
|
|
38
58
|
export class SalaamService {
|
|
59
|
+
readonly external: ExternalWork;
|
|
60
|
+
a2aInbound?: A2AInbound;
|
|
39
61
|
readonly stateDir: string;
|
|
40
62
|
readonly socket: string;
|
|
41
63
|
readonly bridgeSocket: string;
|
|
@@ -54,6 +76,10 @@ export class SalaamService {
|
|
|
54
76
|
readonly dispatcher: Dispatcher;
|
|
55
77
|
readonly managed: ManagedWork;
|
|
56
78
|
readonly children: ChildWork;
|
|
79
|
+
readonly agents: AgentProfiles;
|
|
80
|
+
readonly repositories: RepositorySnapshots;
|
|
81
|
+
readonly team: TeamWork;
|
|
82
|
+
readonly workerWorkspaces: WorkerWorkspaces;
|
|
57
83
|
readonly workspaces: Workspaces;
|
|
58
84
|
readonly coding: CodingWork;
|
|
59
85
|
readonly codingSocket: string | null;
|
|
@@ -82,6 +108,13 @@ export class SalaamService {
|
|
|
82
108
|
this.tasks = new TaskContext(this.store, this.artifacts);
|
|
83
109
|
this.taskWork = new TaskWork(this.store, this.tasks);
|
|
84
110
|
this.taskBudget = new TaskBudget(this.store, this.taskWork);
|
|
111
|
+
if (options.admitModelRequest) {
|
|
112
|
+
const taskAdmission = this.store.taskModelAdmission!;
|
|
113
|
+
this.store.taskModelAdmission = (session, attempt, input, output) => {
|
|
114
|
+
taskAdmission(session, attempt, input, output);
|
|
115
|
+
options.admitModelRequest!(session, attempt, input, output);
|
|
116
|
+
};
|
|
117
|
+
}
|
|
85
118
|
this.workspaces = new Workspaces(this.store, this.artifacts, options.codingRoot);
|
|
86
119
|
if (options.codingSocket && !options.codingRoot)
|
|
87
120
|
throw new Error('Command execution requires an explicit --coding-root');
|
|
@@ -90,6 +123,35 @@ export class SalaamService {
|
|
|
90
123
|
(options.codingSocket
|
|
91
124
|
? new DockerCodingExecutor(engineRequest(options.codingSocket))
|
|
92
125
|
: undefined);
|
|
126
|
+
if (
|
|
127
|
+
options.harnessConfig &&
|
|
128
|
+
(options.externalBackend ||
|
|
129
|
+
!(executor instanceof DockerCodingExecutor) ||
|
|
130
|
+
!options.codingSocket ||
|
|
131
|
+
!options.codingRoot)
|
|
132
|
+
)
|
|
133
|
+
throw Error(
|
|
134
|
+
'Harness configuration requires the real configured Docker executor and an exclusive backend',
|
|
135
|
+
);
|
|
136
|
+
if (options.a2aConfig && (options.externalBackend || !options.codingRoot || !executor))
|
|
137
|
+
throw Error(
|
|
138
|
+
'A2A configuration requires an owned coding root/executor and an exclusive backend',
|
|
139
|
+
);
|
|
140
|
+
const externalBackend =
|
|
141
|
+
options.externalBackend ??
|
|
142
|
+
new ExternalBackends([
|
|
143
|
+
...(options.harnessConfig
|
|
144
|
+
? [
|
|
145
|
+
new DockerHarnessBackend(
|
|
146
|
+
resolve(options.harnessConfig),
|
|
147
|
+
join(resolve(options.codingRoot!), 'external'),
|
|
148
|
+
options.codingSocket!,
|
|
149
|
+
executor as DockerCodingExecutor,
|
|
150
|
+
),
|
|
151
|
+
]
|
|
152
|
+
: []),
|
|
153
|
+
...(options.a2aConfig ? [new A2ABackend(resolve(options.a2aConfig))] : []),
|
|
154
|
+
]);
|
|
93
155
|
this.coding = new CodingWork(
|
|
94
156
|
this.store,
|
|
95
157
|
this.workspaces,
|
|
@@ -119,18 +181,92 @@ export class SalaamService {
|
|
|
119
181
|
effectsEnabled: !!options.effectConnector,
|
|
120
182
|
});
|
|
121
183
|
this.supervisor.tasks = this.tasks;
|
|
184
|
+
this.taskWork.providerCheck = (limits) =>
|
|
185
|
+
this.supervisor.assertTaskProviderSupported(limits);
|
|
122
186
|
this.supervisor.coding = this.coding;
|
|
123
187
|
this.managed = new ManagedWork(this.store, {
|
|
124
188
|
connector: options.effectConnector,
|
|
125
189
|
approvalTtlMs: options.approvalTtlMs,
|
|
126
|
-
assertActive: (id, epoch, attempt) =>
|
|
190
|
+
assertActive: (id, epoch, attempt) =>
|
|
191
|
+
this.external?.owns(id)
|
|
192
|
+
? this.external.assertActive(id, epoch, attempt)
|
|
193
|
+
: this.supervisor.assertActive(id, epoch, attempt),
|
|
127
194
|
reply: (id, epoch, request, confirmed) =>
|
|
128
195
|
this.supervisor.replyUI(id, epoch, request, confirmed),
|
|
129
|
-
failed: (id, error) =>
|
|
196
|
+
failed: (id, error) =>
|
|
197
|
+
this.external?.owns(id)
|
|
198
|
+
? this.external.failControl(id, error)
|
|
199
|
+
: this.supervisor.failControl(id, error),
|
|
130
200
|
});
|
|
131
201
|
this.supervisor.managed = this.managed;
|
|
132
|
-
this.
|
|
202
|
+
this.agents = new AgentProfiles(
|
|
203
|
+
this.store,
|
|
204
|
+
this.supervisor.route,
|
|
205
|
+
async (route, session) => {
|
|
206
|
+
const prepared = await this.supervisor.prepareProviderRoute(route);
|
|
207
|
+
const task = this.taskWork.forSession(session);
|
|
208
|
+
if (task) this.supervisor.assertTaskProviderSupported(task.limits, prepared.output_cap);
|
|
209
|
+
return { route: prepared.route, output_cap: prepared.output_cap };
|
|
210
|
+
},
|
|
211
|
+
);
|
|
212
|
+
this.store.workerBoundary.profiles = this.agents;
|
|
213
|
+
this.agents.validateFrame = (session) => {
|
|
214
|
+
this.tasks.frame(session, 'x'.repeat(32));
|
|
215
|
+
};
|
|
216
|
+
this.repositories = new RepositorySnapshots(this.store, this.artifacts, this.workspaces);
|
|
217
|
+
this.supervisor.repositories = this.repositories;
|
|
218
|
+
this.children = new ChildWork(
|
|
219
|
+
this.store,
|
|
220
|
+
this.artifacts,
|
|
221
|
+
this.supervisor,
|
|
222
|
+
this.repositories,
|
|
223
|
+
);
|
|
133
224
|
this.supervisor.children = this.children;
|
|
225
|
+
this.workerWorkspaces = new WorkerWorkspaces(
|
|
226
|
+
this.store,
|
|
227
|
+
this.workspaces,
|
|
228
|
+
this.repositories,
|
|
229
|
+
this.artifacts,
|
|
230
|
+
this.coding,
|
|
231
|
+
this.children,
|
|
232
|
+
);
|
|
233
|
+
this.children.workspaces = this.workerWorkspaces;
|
|
234
|
+
this.agents.assertExecution = () => {
|
|
235
|
+
this.workerWorkspaces.execution();
|
|
236
|
+
};
|
|
237
|
+
this.store.workerBoundary.assertIsolation = (binding) =>
|
|
238
|
+
this.workerWorkspaces.assert(binding);
|
|
239
|
+
this.taskWork.assertStepReview = (task, step) =>
|
|
240
|
+
this.workerWorkspaces.assertStepReview(task, step);
|
|
241
|
+
this.team = new TeamWork(
|
|
242
|
+
this.store,
|
|
243
|
+
this.taskWork,
|
|
244
|
+
this.taskBudget,
|
|
245
|
+
this.children,
|
|
246
|
+
this.agents,
|
|
247
|
+
);
|
|
248
|
+
this.supervisor.team = this.team;
|
|
249
|
+
this.external = new ExternalWork(
|
|
250
|
+
this.store,
|
|
251
|
+
this.artifacts,
|
|
252
|
+
this.children,
|
|
253
|
+
this.taskBudget,
|
|
254
|
+
this.managed,
|
|
255
|
+
this.workspaces,
|
|
256
|
+
externalBackend,
|
|
257
|
+
options.admitModelRequest,
|
|
258
|
+
);
|
|
259
|
+
this.children.external = this.external;
|
|
260
|
+
const guardSubmission = this.store.guardTaskSubmission!;
|
|
261
|
+
this.store.guardTaskSubmission = (session, step) => {
|
|
262
|
+
const child = this.children.owned(session);
|
|
263
|
+
if (child && this.store.workerBoundary.view(child.id).binding.external)
|
|
264
|
+
throw new ServiceError(
|
|
265
|
+
'external_worker_owned',
|
|
266
|
+
'Use the owning team assignment and an explicit continuation',
|
|
267
|
+
);
|
|
268
|
+
guardSubmission(session, step);
|
|
269
|
+
};
|
|
134
270
|
this.dispatcher = new Dispatcher(this.store, this.supervisor);
|
|
135
271
|
this.taskDriver = new TaskDriver(
|
|
136
272
|
this.store,
|
|
@@ -150,6 +286,7 @@ export class SalaamService {
|
|
|
150
286
|
);
|
|
151
287
|
this.recurring = new RecurringWork(this.jobs);
|
|
152
288
|
this.inbox = new OperatorInbox(this.store, (error) => {
|
|
289
|
+
this.external.failAll(error);
|
|
153
290
|
for (const session of this.store.sessions())
|
|
154
291
|
this.supervisor.failControl(session.id, error);
|
|
155
292
|
});
|
|
@@ -207,19 +344,27 @@ export class SalaamService {
|
|
|
207
344
|
service.coding.executor?.engineId !== options.expectedExecution.engine_id)
|
|
208
345
|
)
|
|
209
346
|
throw new Error('Host executor does not match reviewed manifest');
|
|
347
|
+
await service.supervisor.providerAuth();
|
|
348
|
+
await service.external.backend?.prepare?.();
|
|
210
349
|
await service.supervisor.recover();
|
|
350
|
+
await service.external.recover();
|
|
211
351
|
service.store.recoverWork();
|
|
212
352
|
service.taskWork.recover();
|
|
213
353
|
service.tasks.recover();
|
|
214
354
|
service.workspaces.recover();
|
|
215
355
|
service.coding.recover();
|
|
216
356
|
service.children.recover();
|
|
357
|
+
service.workerWorkspaces.recover();
|
|
217
358
|
service.managed.recover();
|
|
218
359
|
await service.managed.reconcileAll();
|
|
219
360
|
service.taskCheckpoints.recover();
|
|
220
361
|
await service.jobs.recover();
|
|
221
362
|
await service.recurring.recover();
|
|
222
363
|
service.inbox.reconcile();
|
|
364
|
+
if (options.a2aListenConfig) {
|
|
365
|
+
service.a2aInbound = new A2AInbound(service, resolve(options.a2aListenConfig));
|
|
366
|
+
await service.a2aInbound.listen();
|
|
367
|
+
}
|
|
223
368
|
await service.listen(service.bridgeServer, service.bridgeSocket);
|
|
224
369
|
await service.listen(service.operator, service.socket);
|
|
225
370
|
service.taskDriver.start();
|
|
@@ -253,6 +398,54 @@ export class SalaamService {
|
|
|
253
398
|
if (this.stopping) throw new ServiceError('stopping', 'Service is stopping', 503);
|
|
254
399
|
const url = new URL(req.url ?? '/', 'http://local');
|
|
255
400
|
const parts = url.pathname.split('/').filter(Boolean);
|
|
401
|
+
if (
|
|
402
|
+
!bridge &&
|
|
403
|
+
req.method === 'GET' &&
|
|
404
|
+
parts[0] === 'v1' &&
|
|
405
|
+
parts[1] === 'a2a' &&
|
|
406
|
+
parts[2] === 'grants' &&
|
|
407
|
+
parts[3] &&
|
|
408
|
+
parts.length === 4
|
|
409
|
+
) {
|
|
410
|
+
this.send(res, 200, A2AInbound.binding(this, parts[3]));
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (!bridge && parts[0] === 'v1' && parts[1] === 'harnesses') {
|
|
414
|
+
if (req.method === 'GET' && parts.length === 2) {
|
|
415
|
+
this.send(res, 200, this.external.runtimes());
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
if (parts[2]) {
|
|
419
|
+
const raw = this.store.db
|
|
420
|
+
.prepare('SELECT * FROM child_tasks WHERE id=?')
|
|
421
|
+
.get(parts[2]) as unknown as import('./protocol.js').ChildTask | undefined;
|
|
422
|
+
if (!raw || !this.store.workerBoundary.view(raw.id).binding.external)
|
|
423
|
+
throw new ServiceError('not_found', 'External worker not found', 404);
|
|
424
|
+
if (req.method === 'GET' && parts.length === 3) {
|
|
425
|
+
this.send(res, 200, {
|
|
426
|
+
child: this.children.inspect(raw.parent_session, raw.id),
|
|
427
|
+
execution: this.external.view(raw.id),
|
|
428
|
+
});
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (req.method === 'POST' && parts.length === 4 && parts[3] === 'reconcile') {
|
|
432
|
+
const body = object(await readJson(req));
|
|
433
|
+
const result = await this.external.reconcile(
|
|
434
|
+
raw,
|
|
435
|
+
string(body.request_id, 'request_id', 128),
|
|
436
|
+
string(body.run_id, 'run_id', 128),
|
|
437
|
+
() =>
|
|
438
|
+
this.store.authorizeControl(raw.parent_session, {
|
|
439
|
+
client_id: string(body.client_id, 'client_id', 128),
|
|
440
|
+
token: string(body.token, 'token', 128),
|
|
441
|
+
}),
|
|
442
|
+
);
|
|
443
|
+
this.send(res, 200, result);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
throw new ServiceError('not_found', 'External worker route not found', 404);
|
|
448
|
+
}
|
|
256
449
|
if (bridge) {
|
|
257
450
|
if (req.method !== 'POST' || parts.length !== 3 || parts[0] !== 'worker')
|
|
258
451
|
throw new ServiceError('not_found', 'Bridge route not found', 404);
|
|
@@ -462,7 +655,169 @@ export class SalaamService {
|
|
|
462
655
|
}
|
|
463
656
|
throw new ServiceError('not_found', 'Job route not found', 404);
|
|
464
657
|
}
|
|
658
|
+
if (
|
|
659
|
+
parts[0] === 'v1' &&
|
|
660
|
+
parts[1] === 'integrations' &&
|
|
661
|
+
parts[2] &&
|
|
662
|
+
parts.length === 4 &&
|
|
663
|
+
parts[3] === 'resolve' &&
|
|
664
|
+
req.method === 'POST'
|
|
665
|
+
) {
|
|
666
|
+
const body = object(await readJson(req)),
|
|
667
|
+
controller = {
|
|
668
|
+
client_id: string(body.client_id, 'client_id', 128),
|
|
669
|
+
token: string(body.token, 'token', 128),
|
|
670
|
+
},
|
|
671
|
+
parent = this.workerWorkspaces.owner(parts[2]);
|
|
672
|
+
this.send(
|
|
673
|
+
res,
|
|
674
|
+
200,
|
|
675
|
+
this.workerWorkspaces.resolve(
|
|
676
|
+
parts[2],
|
|
677
|
+
string(body.request_id, 'request_id', 128),
|
|
678
|
+
integer(body.workspace_revision, 'workspace_revision'),
|
|
679
|
+
string(body.workspace_digest, 'workspace_digest', 128),
|
|
680
|
+
() => this.store.authorizeControl(parent, controller),
|
|
681
|
+
),
|
|
682
|
+
);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
if (
|
|
686
|
+
parts[0] === 'v1' &&
|
|
687
|
+
parts[1] === 'integrations' &&
|
|
688
|
+
parts[2] &&
|
|
689
|
+
parts.length === 3 &&
|
|
690
|
+
req.method === 'GET'
|
|
691
|
+
) {
|
|
692
|
+
this.send(res, 200, this.workerWorkspaces.inspect(parts[2]));
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
if (parts[0] === 'v1' && parts[1] === 'workers' && parts[2] && parts.length === 4) {
|
|
696
|
+
const child = this.store.db
|
|
697
|
+
.prepare('SELECT parent_session FROM child_tasks WHERE id=?')
|
|
698
|
+
.get(parts[2]);
|
|
699
|
+
if (!child) throw new ServiceError('not_found', 'Worker not found', 404);
|
|
700
|
+
const parent = String(child.parent_session);
|
|
701
|
+
if (parts[3] === 'proposal' && req.method === 'GET') {
|
|
702
|
+
this.send(
|
|
703
|
+
res,
|
|
704
|
+
200,
|
|
705
|
+
this.workerWorkspaces.proposal(
|
|
706
|
+
parent,
|
|
707
|
+
parts[2],
|
|
708
|
+
integer(Number(url.searchParams.get('offset') ?? '0'), 'offset'),
|
|
709
|
+
),
|
|
710
|
+
);
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
if (parts[3] === 'integrate' && req.method === 'POST') {
|
|
714
|
+
const body = object(await readJson(req)),
|
|
715
|
+
controller = {
|
|
716
|
+
client_id: string(body.client_id, 'client_id', 128),
|
|
717
|
+
token: string(body.token, 'token', 128),
|
|
718
|
+
};
|
|
719
|
+
this.send(
|
|
720
|
+
res,
|
|
721
|
+
200,
|
|
722
|
+
this.workerWorkspaces.integrate(
|
|
723
|
+
parent,
|
|
724
|
+
parts[2],
|
|
725
|
+
string(body.request_id, 'request_id', 128),
|
|
726
|
+
integer(body.workspace_revision, 'workspace_revision'),
|
|
727
|
+
string(body.workspace_digest, 'workspace_digest', 128),
|
|
728
|
+
string(body.patch_digest, 'patch_digest', 128),
|
|
729
|
+
() => this.store.authorizeControl(parent, controller),
|
|
730
|
+
),
|
|
731
|
+
);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
465
735
|
if (parts[0] === 'v1' && parts[1] === 'tasks') {
|
|
736
|
+
if (parts[2] && parts[3] === 'team') {
|
|
737
|
+
const task = this.taskWork.get(parts[2]);
|
|
738
|
+
if (req.method === 'GET' && parts.length === 4) {
|
|
739
|
+
this.send(res, 200, this.team.view(task.id));
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
if (req.method === 'POST') {
|
|
743
|
+
const body = object(await readJson(req));
|
|
744
|
+
const request = string(body.request_id, 'request_id', 128);
|
|
745
|
+
const controller = {
|
|
746
|
+
client_id: string(body.client_id, 'client_id', 128),
|
|
747
|
+
token: string(body.token, 'token', 128),
|
|
748
|
+
};
|
|
749
|
+
const authorize = () => this.store.authorizeControl(task.session_id, controller);
|
|
750
|
+
let result: unknown;
|
|
751
|
+
if (parts.length === 5 && parts[4] === 'assign') {
|
|
752
|
+
if (body.runtime !== undefined && body.requests !== undefined)
|
|
753
|
+
throw new ServiceError(
|
|
754
|
+
'worker_budget_scope',
|
|
755
|
+
'Use invocations for an external runtime; requests always means model requests',
|
|
756
|
+
400,
|
|
757
|
+
);
|
|
758
|
+
result = this.team.assign(
|
|
759
|
+
task.id,
|
|
760
|
+
string(body.step_id, 'step_id', 128),
|
|
761
|
+
string(body.profile_id, 'profile_id', 128),
|
|
762
|
+
integer(
|
|
763
|
+
body.runtime === undefined ? body.requests : body.invocations,
|
|
764
|
+
body.runtime === undefined ? 'requests' : 'invocations',
|
|
765
|
+
),
|
|
766
|
+
request,
|
|
767
|
+
integer(body.expected_revision, 'expected_revision'),
|
|
768
|
+
controller,
|
|
769
|
+
body.runtime === undefined ? undefined : string(body.runtime, 'runtime', 128),
|
|
770
|
+
);
|
|
771
|
+
} else if (parts.length === 5 && parts[4] === 'send') {
|
|
772
|
+
result = this.team.send(
|
|
773
|
+
task.id,
|
|
774
|
+
'operator',
|
|
775
|
+
string(body.recipient, 'recipient', 128),
|
|
776
|
+
string(body.text, 'text', 4096),
|
|
777
|
+
request,
|
|
778
|
+
authorize,
|
|
779
|
+
);
|
|
780
|
+
} else if (parts.length === 6 && parts[4]) {
|
|
781
|
+
const assignment = this.team.get(parts[4]);
|
|
782
|
+
if (assignment.task_id !== task.id)
|
|
783
|
+
throw new ServiceError('team_message_scope', 'Assignment belongs to another task');
|
|
784
|
+
if (parts[5] === 'run')
|
|
785
|
+
result = this.team.run(
|
|
786
|
+
assignment.id,
|
|
787
|
+
request,
|
|
788
|
+
integer(body.expected_revision, 'expected_revision'),
|
|
789
|
+
integer(body.generation, 'generation'),
|
|
790
|
+
body.instruction == null ? null : string(body.instruction, 'instruction', 4096),
|
|
791
|
+
body.continues_attempt_id == null
|
|
792
|
+
? null
|
|
793
|
+
: string(body.continues_attempt_id, 'continues_attempt_id', 128),
|
|
794
|
+
controller,
|
|
795
|
+
);
|
|
796
|
+
else if (parts[5] === 'reject')
|
|
797
|
+
result = this.team.reject(
|
|
798
|
+
assignment.id,
|
|
799
|
+
request,
|
|
800
|
+
integer(body.expected_revision, 'expected_revision'),
|
|
801
|
+
integer(body.generation, 'generation'),
|
|
802
|
+
string(body.reason, 'reason', 4096),
|
|
803
|
+
controller,
|
|
804
|
+
);
|
|
805
|
+
else if (parts[5] === 'receive')
|
|
806
|
+
result = this.team.receive(assignment.id, request, authorize);
|
|
807
|
+
else if (parts[5] === 'ack')
|
|
808
|
+
result = this.team.acknowledge(
|
|
809
|
+
assignment.id,
|
|
810
|
+
string(body.message_id, 'message_id', 128),
|
|
811
|
+
request,
|
|
812
|
+
authorize,
|
|
813
|
+
);
|
|
814
|
+
else throw new ServiceError('not_found', 'Team action not found', 404);
|
|
815
|
+
} else throw new ServiceError('not_found', 'Team route not found', 404);
|
|
816
|
+
this.send(res, 200, result);
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
throw new ServiceError('not_found', 'Team route not found', 404);
|
|
820
|
+
}
|
|
466
821
|
if (req.method === 'GET' && parts.length === 2) {
|
|
467
822
|
const session = string(url.searchParams.get('session_id'), 'session_id', 128);
|
|
468
823
|
this.store.session(session);
|
|
@@ -640,6 +995,26 @@ export class SalaamService {
|
|
|
640
995
|
const sessions = this.store.sessions().map((x) => this.supervisor.inspect(x.id));
|
|
641
996
|
this.send(res, 200, {
|
|
642
997
|
service: 'healthy',
|
|
998
|
+
a2a: {
|
|
999
|
+
inbound_enabled: !!this.a2aInbound,
|
|
1000
|
+
listener: this.a2aInbound?.server.address() ?? null,
|
|
1001
|
+
wire_version: '1.0',
|
|
1002
|
+
remote_execution: 'unobserved',
|
|
1003
|
+
inbound_authority: 'one-use-native-analysis-assignment',
|
|
1004
|
+
},
|
|
1005
|
+
external_workers: {
|
|
1006
|
+
control: this.external.status(),
|
|
1007
|
+
runtimes: this.external.runtimes(),
|
|
1008
|
+
opaque_budget: 'explicit-invocations-only',
|
|
1009
|
+
unknown_model_usage: true,
|
|
1010
|
+
uncertain_stops: Number(
|
|
1011
|
+
this.store.db
|
|
1012
|
+
.prepare(
|
|
1013
|
+
"SELECT count(*) AS n FROM external_runs WHERE stopped=0 AND state='unknown'",
|
|
1014
|
+
)
|
|
1015
|
+
.get()!.n,
|
|
1016
|
+
),
|
|
1017
|
+
},
|
|
643
1018
|
job_execution: {
|
|
644
1019
|
durable_jobs: true,
|
|
645
1020
|
one_shot_triggers: !!this.coding.executor,
|
|
@@ -651,16 +1026,18 @@ export class SalaamService {
|
|
|
651
1026
|
control_error: this.jobs.controlError,
|
|
652
1027
|
},
|
|
653
1028
|
task_execution: {
|
|
654
|
-
durable_tasks:
|
|
1029
|
+
durable_tasks: this.supervisor.providerCapabilities().durable_tasks,
|
|
655
1030
|
explicit_steps: true,
|
|
656
1031
|
automatic_progression: 'operator-bounded-opt-in',
|
|
657
1032
|
verified_completion: !!this.coding.executor,
|
|
658
1033
|
verification_supported: true,
|
|
659
1034
|
task_budgets: true,
|
|
1035
|
+
task_token_budgets: this.supervisor.providerCapabilities().output_token_cap,
|
|
660
1036
|
checkpoints: true,
|
|
661
1037
|
deterministic_compaction: 'operator-bounded-idle-only',
|
|
662
1038
|
},
|
|
663
1039
|
provider_route: this.supervisor.route,
|
|
1040
|
+
provider_capabilities: this.supervisor.providerCapabilities(),
|
|
664
1041
|
effect_connector: this.managed.options.connector?.id ?? null,
|
|
665
1042
|
coding_root: this.workspaces.root ?? null,
|
|
666
1043
|
coding_socket: this.codingSocket,
|
|
@@ -750,6 +1127,60 @@ export class SalaamService {
|
|
|
750
1127
|
}
|
|
751
1128
|
if (parts[0] === 'v1' && parts[1] === 'sessions' && parts[2]) {
|
|
752
1129
|
const id = parts[2];
|
|
1130
|
+
if (parts[3] === 'agents') {
|
|
1131
|
+
if (req.method === 'GET') {
|
|
1132
|
+
const profile = url.searchParams.get('profile');
|
|
1133
|
+
this.send(
|
|
1134
|
+
res,
|
|
1135
|
+
200,
|
|
1136
|
+
profile
|
|
1137
|
+
? {
|
|
1138
|
+
...this.agents.inspect(id, profile),
|
|
1139
|
+
digest: digest(this.agents.inspect(id, profile)),
|
|
1140
|
+
}
|
|
1141
|
+
: this.agents.list(id).map((p) => ({ ...p, digest: digest(p) })),
|
|
1142
|
+
);
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
const body = object(await readJson(req));
|
|
1146
|
+
const controller = {
|
|
1147
|
+
client_id: string(body.client_id, 'client_id', 128),
|
|
1148
|
+
token: string(body.token, 'token', 128),
|
|
1149
|
+
};
|
|
1150
|
+
const request = string(body.request_id, 'request_id', 128);
|
|
1151
|
+
if (parts[4] === 'admit') {
|
|
1152
|
+
const facts =
|
|
1153
|
+
body.fact_ids === undefined
|
|
1154
|
+
? []
|
|
1155
|
+
: Array.isArray(body.fact_ids)
|
|
1156
|
+
? body.fact_ids.map((x) => string(x, 'fact_id', 128))
|
|
1157
|
+
: (() => {
|
|
1158
|
+
throw new ServiceError('invalid_field', 'fact_ids must be an array');
|
|
1159
|
+
})();
|
|
1160
|
+
this.send(
|
|
1161
|
+
res,
|
|
1162
|
+
201,
|
|
1163
|
+
await this.agents.admit(
|
|
1164
|
+
id,
|
|
1165
|
+
request,
|
|
1166
|
+
string(body.profile_id, 'profile_id', 128),
|
|
1167
|
+
string(body.expected_digest, 'expected_digest', 64),
|
|
1168
|
+
facts,
|
|
1169
|
+
controller,
|
|
1170
|
+
),
|
|
1171
|
+
);
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
if (parts[4] === 'revoke') {
|
|
1175
|
+
this.send(
|
|
1176
|
+
res,
|
|
1177
|
+
200,
|
|
1178
|
+
this.agents.revoke(id, string(body.grant_id, 'grant_id', 128), request, controller),
|
|
1179
|
+
);
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
throw new ServiceError('not_found', 'Agent operation not found', 404);
|
|
1183
|
+
}
|
|
753
1184
|
if (parts[3] === 'activity' && req.method === 'GET') {
|
|
754
1185
|
this.store.session(id);
|
|
755
1186
|
const rows = this.store.db
|
|
@@ -1011,12 +1442,14 @@ export class SalaamService {
|
|
|
1011
1442
|
},
|
|
1012
1443
|
);
|
|
1013
1444
|
this.coding.cancelAttempt(attempt);
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
this.
|
|
1019
|
-
|
|
1445
|
+
const owned = this.children.owned(this.store.attempt(attempt).session_id);
|
|
1446
|
+
void (
|
|
1447
|
+
owned && this.store.workerBoundary.view(owned.id).binding.external
|
|
1448
|
+
? this.external.cancel(owned)
|
|
1449
|
+
: this.children.cancelOwned(attempt).then(() => this.supervisor.cancelAttempt(attempt))
|
|
1450
|
+
).catch((error) =>
|
|
1451
|
+
this.supervisor.failControl(this.store.attempt(attempt).session_id, error),
|
|
1452
|
+
);
|
|
1020
1453
|
this.send(res, 202, receipt);
|
|
1021
1454
|
return;
|
|
1022
1455
|
}
|
|
@@ -1255,6 +1688,8 @@ export class SalaamService {
|
|
|
1255
1688
|
this.children.pause();
|
|
1256
1689
|
const errors: unknown[] = [];
|
|
1257
1690
|
try {
|
|
1691
|
+
await this.a2aInbound?.close();
|
|
1692
|
+
await this.external.close();
|
|
1258
1693
|
await this.coding.close();
|
|
1259
1694
|
try {
|
|
1260
1695
|
await this.children.close();
|