@superjackfruitlabs/salaam 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +25 -17
- package/dist/src/a2a-backend.js +225 -0
- package/dist/src/a2a-inbound.js +487 -0
- package/dist/src/a2a-peer.js +370 -0
- package/dist/src/a2a-protocol.js +250 -0
- package/dist/src/agent-profiles.js +289 -0
- package/dist/src/children.js +132 -26
- package/dist/src/claude-provider.js +2 -179
- package/dist/src/cli-help.js +107 -6
- package/dist/src/cli.js +477 -13
- package/dist/src/config.js +26 -3
- package/dist/src/credentials.js +7 -2
- package/dist/src/external-runtime.js +1 -0
- package/dist/src/external-work.js +583 -0
- package/dist/src/harness-backend.js +466 -0
- package/dist/src/harness-container.js +450 -0
- package/dist/src/harness-model-gateway.js +104 -0
- package/dist/src/harness-prepare.js +88 -0
- package/dist/src/harness-probe.js +111 -0
- package/dist/src/harness-protocol.js +325 -0
- package/dist/src/harness-readiness.js +50 -0
- package/dist/src/harness-rpc.js +206 -0
- package/dist/src/harness-tool-identity.js +87 -0
- package/dist/src/harness-wire-bridge.js +387 -0
- package/dist/src/host.js +6 -1
- package/dist/src/house-extension.js +147 -64
- package/dist/src/live-provider.js +301 -0
- package/dist/src/managed.js +33 -3
- package/dist/src/protocol.js +9 -5
- package/dist/src/provider-auth.js +300 -0
- package/dist/src/provider-config.js +65 -17
- package/dist/src/provider-model.js +78 -0
- package/dist/src/provider-payload.js +264 -0
- package/dist/src/provider-transport.js +36 -0
- package/dist/src/provider-usage.js +59 -0
- package/dist/src/repository-snapshots.js +219 -0
- package/dist/src/service.js +274 -9
- package/dist/src/store.js +44 -2
- package/dist/src/supervisor.js +246 -35
- package/dist/src/task-budget.js +17 -8
- package/dist/src/task-context.js +15 -0
- package/dist/src/task-driver.js +38 -14
- package/dist/src/task-policy.js +20 -1
- package/dist/src/task-verification.js +3 -0
- package/dist/src/task-work.js +31 -0
- package/dist/src/team-work.js +378 -0
- package/dist/src/tui-view.js +65 -10
- package/dist/src/tui.js +4 -2
- package/dist/src/usage.js +18 -0
- package/dist/src/worker-boundary.js +298 -0
- package/dist/src/worker-workspaces.js +274 -0
- package/dist/src/workspaces.js +6 -2
- package/harness-runtime/codex-pins.json +10 -0
- package/harness-runtime/package-lock.json +560 -0
- package/harness-runtime/package.json +9 -0
- package/npm-shrinkwrap.json +57 -2
- package/package.json +2 -1
- package/src/a2a-backend.ts +254 -0
- package/src/a2a-inbound.ts +585 -0
- package/src/a2a-peer.ts +441 -0
- package/src/a2a-protocol.ts +286 -0
- package/src/agent-profiles.ts +379 -0
- package/src/children.ts +168 -27
- package/src/claude-provider.ts +2 -189
- package/src/cli-help.ts +107 -6
- package/src/cli.ts +602 -12
- package/src/config.ts +34 -3
- package/src/credentials.ts +9 -2
- package/src/external-runtime.ts +54 -0
- package/src/external-work.ts +663 -0
- package/src/harness-backend.ts +557 -0
- package/src/harness-container.ts +543 -0
- package/src/harness-model-gateway.ts +138 -0
- package/src/harness-prepare.ts +113 -0
- package/src/harness-probe.ts +150 -0
- package/src/harness-protocol.ts +388 -0
- package/src/harness-readiness.ts +71 -0
- package/src/harness-rpc.ts +244 -0
- package/src/harness-tool-identity.ts +91 -0
- package/src/harness-wire-bridge.ts +418 -0
- package/src/host.ts +11 -6
- package/src/house-extension.ts +165 -66
- package/src/live-provider.ts +347 -0
- package/src/managed.ts +43 -4
- package/src/protocol.ts +36 -8
- package/src/provider-auth.ts +336 -0
- package/src/provider-config.ts +89 -18
- package/src/provider-model.ts +86 -0
- package/src/provider-payload.ts +263 -0
- package/src/provider-transport.ts +51 -0
- package/src/provider-usage.ts +64 -0
- package/src/repository-snapshots.ts +259 -0
- package/src/service.ts +446 -11
- package/src/store.ts +60 -4
- package/src/supervisor.ts +294 -41
- package/src/task-budget.ts +24 -11
- package/src/task-context.ts +17 -0
- package/src/task-driver.ts +51 -15
- package/src/task-policy.ts +23 -3
- package/src/task-verification.ts +2 -0
- package/src/task-work.ts +46 -1
- package/src/team-work.ts +554 -0
- package/src/tui-client.ts +5 -0
- package/src/tui-view.ts +164 -12
- package/src/tui.ts +5 -2
- package/src/usage.ts +17 -0
- package/src/worker-boundary.ts +395 -0
- package/src/worker-workspaces.ts +390 -0
- package/src/workspaces.ts +4 -2
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { canonical, digest, ServiceError } from './protocol.js';
|
|
2
|
+
import { hostDigests } from './host.js';
|
|
3
|
+
import {
|
|
4
|
+
A2AHttp,
|
|
5
|
+
A2A_PIN,
|
|
6
|
+
a2aObject,
|
|
7
|
+
privateJSON,
|
|
8
|
+
validatePeer,
|
|
9
|
+
type A2APeer,
|
|
10
|
+
} from './a2a-peer.js';
|
|
11
|
+
import { A2AInvocation, type A2ACheckpoint } from './a2a-protocol.js';
|
|
12
|
+
import type { ExternalBackend, ExternalHooks, ExternalRuntime } from './external-runtime.js';
|
|
13
|
+
import type { WorkerBinding } from './worker-boundary.js';
|
|
14
|
+
import type { WorkspaceTree } from './workspaces.js';
|
|
15
|
+
|
|
16
|
+
/** Bounded outbound analysis. A peer has no local file/command/controller authority. */
|
|
17
|
+
export class A2ABackend implements ExternalBackend {
|
|
18
|
+
private peers: A2APeer[];
|
|
19
|
+
private ready = false;
|
|
20
|
+
private active = new Map<string, A2AInvocation>();
|
|
21
|
+
private trees = new Map<string, WorkspaceTree>();
|
|
22
|
+
constructor(readonly config: string) {
|
|
23
|
+
this.peers = this.read();
|
|
24
|
+
}
|
|
25
|
+
private read(): A2APeer[] {
|
|
26
|
+
const config = a2aObject(privateJSON(this.config));
|
|
27
|
+
if (
|
|
28
|
+
Object.keys(config).sort().join(',') !== 'peers,schema' ||
|
|
29
|
+
config.schema !== 1 ||
|
|
30
|
+
!Array.isArray(config.peers) ||
|
|
31
|
+
!config.peers.length ||
|
|
32
|
+
config.peers.length > 16
|
|
33
|
+
)
|
|
34
|
+
throw Error('Expected bounded A2A peer registry schema 1');
|
|
35
|
+
const peers = config.peers.map(validatePeer);
|
|
36
|
+
if (new Set(peers.map((p) => p.id)).size !== peers.length)
|
|
37
|
+
throw Error('Duplicate peer identity');
|
|
38
|
+
return peers;
|
|
39
|
+
}
|
|
40
|
+
private descriptor(p: A2APeer): ExternalRuntime {
|
|
41
|
+
return {
|
|
42
|
+
id: p.id,
|
|
43
|
+
kind: 'a2a',
|
|
44
|
+
target: 'a2a',
|
|
45
|
+
version: A2A_PIN.wire,
|
|
46
|
+
config_digest: digest(p),
|
|
47
|
+
adapter_digest: hostDigests().build_digest,
|
|
48
|
+
execution_identity: {
|
|
49
|
+
version: A2A_PIN.wire,
|
|
50
|
+
kernel: 'remote-unobserved',
|
|
51
|
+
arch: 'remote-unobserved',
|
|
52
|
+
},
|
|
53
|
+
boundary_digest: digest({ protocol: A2A_PIN, peer: p, authority: 'remote-analysis-only' }),
|
|
54
|
+
engine_id: p.id,
|
|
55
|
+
evidence: 'proven',
|
|
56
|
+
model_usage: 'unknown',
|
|
57
|
+
output_cap: null,
|
|
58
|
+
max_model_requests: null,
|
|
59
|
+
files: false,
|
|
60
|
+
commands: false,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
async prepare(): Promise<void> {
|
|
64
|
+
this.peers = this.read();
|
|
65
|
+
this.ready = true;
|
|
66
|
+
}
|
|
67
|
+
async close(): Promise<void> {
|
|
68
|
+
this.active.clear();
|
|
69
|
+
this.trees.clear();
|
|
70
|
+
this.ready = false;
|
|
71
|
+
}
|
|
72
|
+
runtimes(): ExternalRuntime[] {
|
|
73
|
+
return this.ready ? this.peers.map((p) => this.descriptor(p)) : [];
|
|
74
|
+
}
|
|
75
|
+
validate(runtime: ExternalRuntime): void {
|
|
76
|
+
const p = this.read().find((p) => p.id === runtime.id);
|
|
77
|
+
if (!this.ready || !p || canonical(this.descriptor(p)) !== canonical(runtime))
|
|
78
|
+
throw Error('A2A peer/config/credential/build admission changed');
|
|
79
|
+
}
|
|
80
|
+
assertAuthority(_runtime: ExternalRuntime, tools: readonly string[]): void {
|
|
81
|
+
if (
|
|
82
|
+
tools.some((t) =>
|
|
83
|
+
[
|
|
84
|
+
'salaam_files',
|
|
85
|
+
'salaam_command',
|
|
86
|
+
'salaam_team',
|
|
87
|
+
'salaam_managed',
|
|
88
|
+
'salaam_children',
|
|
89
|
+
].includes(t),
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
throw Error('A2A peers are analysis specialists without local tools');
|
|
93
|
+
}
|
|
94
|
+
async execute(
|
|
95
|
+
binding: WorkerBinding,
|
|
96
|
+
run: string,
|
|
97
|
+
prompt: string,
|
|
98
|
+
tree: WorkspaceTree,
|
|
99
|
+
hooks: ExternalHooks,
|
|
100
|
+
): Promise<{ result: unknown }> {
|
|
101
|
+
this.validate(binding.external!);
|
|
102
|
+
this.assertAuthority(binding.external!, binding.tools);
|
|
103
|
+
hooks.assertActive();
|
|
104
|
+
if (this.active.has(run) || this.trees.has(run))
|
|
105
|
+
throw Error('A2A invocation cannot be replayed');
|
|
106
|
+
const p = this.peers.find((p) => p.id === binding.external!.id)!,
|
|
107
|
+
http = new A2AHttp(p);
|
|
108
|
+
const files: Record<string, string> = Object.create(null),
|
|
109
|
+
disclosure: A2ACheckpoint['disclosure'] = [];
|
|
110
|
+
for (const path of p.files) {
|
|
111
|
+
const f = tree[path];
|
|
112
|
+
if (!f) throw Error('An explicitly disclosed repository file is absent');
|
|
113
|
+
const bytes = Buffer.from(f.content, 'base64'),
|
|
114
|
+
text = bytes.toString('utf8');
|
|
115
|
+
if (!Buffer.from(text).equals(bytes) || bytes.length > 65536)
|
|
116
|
+
throw Error('Only bounded UTF-8 peer disclosure is supported');
|
|
117
|
+
files[path] = text;
|
|
118
|
+
disclosure.push({ path, digest: digest(f), bytes: bytes.length });
|
|
119
|
+
}
|
|
120
|
+
const data = { skill: p.skill, files },
|
|
121
|
+
peer = { id: p.id, tenant: p.tenant, digest: digest(p), cancel: p.cancel, skill: p.skill };
|
|
122
|
+
if (canonical({ prompt, data }).length > 196608) throw Error('A2A disclosure exceeds bound');
|
|
123
|
+
const initial = A2AInvocation.prepare(
|
|
124
|
+
binding.worker_id,
|
|
125
|
+
binding.attempt_id,
|
|
126
|
+
peer,
|
|
127
|
+
prompt,
|
|
128
|
+
data,
|
|
129
|
+
disclosure,
|
|
130
|
+
);
|
|
131
|
+
initial.run_id = run;
|
|
132
|
+
// Both identity and disclosure are durable before any SendMessage bytes leave.
|
|
133
|
+
hooks.checkpoint(initial);
|
|
134
|
+
hooks.receipt(initial);
|
|
135
|
+
const invocation = new A2AInvocation(peer, initial, http, (cp) => {
|
|
136
|
+
hooks.checkpoint(cp);
|
|
137
|
+
hooks.receipt(cp);
|
|
138
|
+
return undefined;
|
|
139
|
+
});
|
|
140
|
+
this.active.set(run, invocation);
|
|
141
|
+
this.trees.set(run, structuredClone(tree));
|
|
142
|
+
await http.card();
|
|
143
|
+
hooks.assertActive();
|
|
144
|
+
let task = await invocation.dispatch(prompt, data);
|
|
145
|
+
const end = Date.now() + 180000;
|
|
146
|
+
while (invocation.view().state !== 'terminal') {
|
|
147
|
+
hooks.assertActive();
|
|
148
|
+
this.validate(binding.external!);
|
|
149
|
+
if (['TASK_STATE_INPUT_REQUIRED', 'TASK_STATE_AUTH_REQUIRED'].includes(task.status.state))
|
|
150
|
+
throw Error('Peer needs input/authentication; explicit operator resolution required');
|
|
151
|
+
if (Date.now() >= end) throw Error('Peer deadline reached; remote work may still run');
|
|
152
|
+
if (p.streaming) {
|
|
153
|
+
try {
|
|
154
|
+
await http.subscribe(invocation.view().task_id!, (event) => {
|
|
155
|
+
hooks.assertActive();
|
|
156
|
+
invocation.observe(event);
|
|
157
|
+
});
|
|
158
|
+
} catch (error) {
|
|
159
|
+
if (error instanceof ServiceError) throw error;
|
|
160
|
+
}
|
|
161
|
+
} else await new Promise((r) => setTimeout(r, 2500));
|
|
162
|
+
hooks.assertActive();
|
|
163
|
+
task = await invocation.inspect();
|
|
164
|
+
}
|
|
165
|
+
if (task.status.state !== 'TASK_STATE_COMPLETED')
|
|
166
|
+
throw Error('Remote task ended without a completed result');
|
|
167
|
+
return {
|
|
168
|
+
result: {
|
|
169
|
+
peer: p.id,
|
|
170
|
+
tenant: p.tenant,
|
|
171
|
+
task,
|
|
172
|
+
disclosure,
|
|
173
|
+
prompt_digest: digest(prompt),
|
|
174
|
+
verified_success: false,
|
|
175
|
+
remote_execution: 'unobserved',
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
collect(run: string): WorkspaceTree {
|
|
180
|
+
const tree = this.trees.get(run);
|
|
181
|
+
if (!tree) throw Error('No owned local snapshot for remote analysis');
|
|
182
|
+
this.trees.delete(run);
|
|
183
|
+
return tree;
|
|
184
|
+
}
|
|
185
|
+
async stop(run: string, runtime: ExternalRuntime, receipt: unknown): Promise<unknown> {
|
|
186
|
+
this.validate(runtime);
|
|
187
|
+
let invocation = this.active.get(run);
|
|
188
|
+
if (!invocation) {
|
|
189
|
+
const record = a2aObject(receipt);
|
|
190
|
+
const cp = a2aObject(record.checkpoint ?? record) as A2ACheckpoint,
|
|
191
|
+
p = this.peers.find((p) => p.id === runtime.id)!;
|
|
192
|
+
if (cp.run_id !== run) throw Error('Remote cleanup receipt belongs to another local run');
|
|
193
|
+
invocation = new A2AInvocation(
|
|
194
|
+
{ id: p.id, tenant: p.tenant, digest: digest(p), cancel: p.cancel, skill: p.skill },
|
|
195
|
+
cp,
|
|
196
|
+
new A2AHttp(p),
|
|
197
|
+
() => undefined,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
if (invocation.view().state === 'prepared') {
|
|
201
|
+
this.active.delete(run);
|
|
202
|
+
this.trees.delete(run);
|
|
203
|
+
return { scope: 'never-dispatched', checkpoint: invocation.view() };
|
|
204
|
+
}
|
|
205
|
+
await invocation.cancel();
|
|
206
|
+
if (invocation.view().state !== 'terminal')
|
|
207
|
+
throw Error('Remote terminal receipt is unconfirmed');
|
|
208
|
+
this.active.delete(run);
|
|
209
|
+
if (invocation.view().result?.status.state !== 'TASK_STATE_COMPLETED') this.trees.delete(run);
|
|
210
|
+
return { scope: 'remote-terminal-receipt', checkpoint: invocation.view() };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export class ExternalBackends implements ExternalBackend {
|
|
215
|
+
constructor(readonly backends: ExternalBackend[]) {}
|
|
216
|
+
async prepare(): Promise<void> {
|
|
217
|
+
for (const b of this.backends) await b.prepare?.();
|
|
218
|
+
const ids = this.runtimes().map((r) => r.id);
|
|
219
|
+
if (new Set(ids).size !== ids.length)
|
|
220
|
+
throw Error('Runtime IDs must be unique across local and remote backends');
|
|
221
|
+
}
|
|
222
|
+
async close(): Promise<void> {
|
|
223
|
+
for (const b of this.backends) await b.close?.();
|
|
224
|
+
}
|
|
225
|
+
runtimes(): ExternalRuntime[] {
|
|
226
|
+
return this.backends.flatMap((b) => b.runtimes());
|
|
227
|
+
}
|
|
228
|
+
private owner(runtime: ExternalRuntime): ExternalBackend {
|
|
229
|
+
const b = this.backends.find((b) => b.runtimes().some((r) => r.id === runtime.id));
|
|
230
|
+
if (!b) throw Error('Runtime is unavailable');
|
|
231
|
+
return b;
|
|
232
|
+
}
|
|
233
|
+
validate(runtime: ExternalRuntime): void {
|
|
234
|
+
this.owner(runtime).validate(runtime);
|
|
235
|
+
}
|
|
236
|
+
assertAuthority(runtime: ExternalRuntime, tools: readonly string[]): void {
|
|
237
|
+
this.owner(runtime).assertAuthority?.(runtime, tools);
|
|
238
|
+
}
|
|
239
|
+
execute(
|
|
240
|
+
binding: WorkerBinding,
|
|
241
|
+
run: string,
|
|
242
|
+
prompt: string,
|
|
243
|
+
tree: WorkspaceTree,
|
|
244
|
+
hooks: ExternalHooks,
|
|
245
|
+
): Promise<{ result: unknown }> {
|
|
246
|
+
return this.owner(binding.external!).execute(binding, run, prompt, tree, hooks);
|
|
247
|
+
}
|
|
248
|
+
collect(run: string, runtime: ExternalRuntime): WorkspaceTree {
|
|
249
|
+
return this.owner(runtime).collect(run, runtime);
|
|
250
|
+
}
|
|
251
|
+
stop(run: string, runtime: ExternalRuntime, receipt: unknown): Promise<unknown> {
|
|
252
|
+
return this.owner(runtime).stop(run, runtime, receipt);
|
|
253
|
+
}
|
|
254
|
+
}
|