@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,259 @@
|
|
|
1
|
+
import { digest, integer, ServiceError, string } from './protocol.js';
|
|
2
|
+
import { readTree, filePath, type Workspaces, type WorkspaceTree } from './workspaces.js';
|
|
3
|
+
import type { Artifacts } from './artifacts.js';
|
|
4
|
+
import type { Store } from './store.js';
|
|
5
|
+
import { workspaceDiff } from './workspace-diff.js';
|
|
6
|
+
import type { WorkerBinding } from './worker-boundary.js';
|
|
7
|
+
|
|
8
|
+
export function repositoryPath(value: unknown): string {
|
|
9
|
+
const path = filePath(value);
|
|
10
|
+
if (
|
|
11
|
+
path
|
|
12
|
+
.split('/')
|
|
13
|
+
.some((part) =>
|
|
14
|
+
/^(?:\.env(?:\..*)?|\.npmrc|\.netrc|\.aws|\.ssh|\.codex|\.gemini|\.claude|\.mcp\.json|\.salaam|\.pi|\.agents|\.state|credentials)$/i.test(
|
|
15
|
+
part,
|
|
16
|
+
),
|
|
17
|
+
)
|
|
18
|
+
)
|
|
19
|
+
throw new ServiceError(
|
|
20
|
+
'unsafe_path',
|
|
21
|
+
'Credential and control files are outside specialist scope',
|
|
22
|
+
);
|
|
23
|
+
return path;
|
|
24
|
+
}
|
|
25
|
+
export function repositoryTree(tree: WorkspaceTree): WorkspaceTree {
|
|
26
|
+
return Object.fromEntries(
|
|
27
|
+
Object.entries(tree).filter(([path]) => {
|
|
28
|
+
try {
|
|
29
|
+
repositoryPath(path);
|
|
30
|
+
return true;
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}),
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
export class RepositorySnapshots {
|
|
38
|
+
constructor(
|
|
39
|
+
readonly store: Store,
|
|
40
|
+
readonly artifacts: Artifacts,
|
|
41
|
+
readonly workspaces: Workspaces,
|
|
42
|
+
) {}
|
|
43
|
+
/** Caller commits these references with the child intent. No live mount is handed to a reader. */
|
|
44
|
+
capture(parent: string, child: string): NonNullable<WorkerBinding['repository']> {
|
|
45
|
+
const workspace = this.workspaces.verify(parent),
|
|
46
|
+
tree = repositoryTree(readTree(workspace.root));
|
|
47
|
+
if (digest(readTree(workspace.root)) !== workspace.digest)
|
|
48
|
+
throw new ServiceError('workspace_changed', 'Workspace changed during specialist admission');
|
|
49
|
+
const base = repositoryTree(
|
|
50
|
+
JSON.parse(this.artifacts.read(workspace.baseline)) as WorkspaceTree,
|
|
51
|
+
);
|
|
52
|
+
const currentArtifact = this.artifacts.prepare(child, JSON.stringify(tree)),
|
|
53
|
+
baseArtifact = this.artifacts.prepare(child, JSON.stringify(base));
|
|
54
|
+
currentArtifact.commit();
|
|
55
|
+
baseArtifact.commit();
|
|
56
|
+
return {
|
|
57
|
+
artifact_id: currentArtifact.id,
|
|
58
|
+
base_artifact: baseArtifact.id,
|
|
59
|
+
digest: digest(tree),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
tree(binding: WorkerBinding): WorkspaceTree {
|
|
63
|
+
if (!binding.repository)
|
|
64
|
+
throw new ServiceError('authority_denied', 'No repository snapshot grant');
|
|
65
|
+
const tree = JSON.parse(this.artifacts.read(binding.repository.artifact_id)) as WorkspaceTree;
|
|
66
|
+
if (digest(tree) !== binding.repository.digest)
|
|
67
|
+
throw new ServiceError('artifact_corrupt', 'Repository snapshot changed');
|
|
68
|
+
return tree;
|
|
69
|
+
}
|
|
70
|
+
read(binding: WorkerBinding, args: Record<string, unknown>): unknown {
|
|
71
|
+
const tree = this.tree(binding),
|
|
72
|
+
action = string(args.action, 'action', 32),
|
|
73
|
+
offset = integer(args.offset ?? 0, 'offset');
|
|
74
|
+
const identity = {
|
|
75
|
+
workspace_id: binding.workspace?.id,
|
|
76
|
+
revision: binding.workspace?.revision,
|
|
77
|
+
digest: binding.repository!.digest,
|
|
78
|
+
artifact_id: binding.repository!.artifact_id,
|
|
79
|
+
};
|
|
80
|
+
if (action === 'inspect') return { ...identity, authority: 'snapshot-read-only' };
|
|
81
|
+
if (action === 'list') {
|
|
82
|
+
const paths = Object.keys(tree).sort();
|
|
83
|
+
return {
|
|
84
|
+
...identity,
|
|
85
|
+
files: paths.slice(offset, offset + 20).map((path) => ({
|
|
86
|
+
path,
|
|
87
|
+
hash: digest(tree[path]),
|
|
88
|
+
bytes: Buffer.from(tree[path]!.content, 'base64').length,
|
|
89
|
+
})),
|
|
90
|
+
next_offset: offset + 20 < paths.length ? offset + 20 : null,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (action === 'read') {
|
|
94
|
+
const path = repositoryPath(args.path),
|
|
95
|
+
file = tree[path];
|
|
96
|
+
if (!file) throw new ServiceError('not_found', 'Snapshot file not found', 404);
|
|
97
|
+
const bytes = Buffer.from(file.content, 'base64'),
|
|
98
|
+
text = bytes.toString('utf8');
|
|
99
|
+
if (text.includes('\0') || !Buffer.from(text).equals(bytes))
|
|
100
|
+
throw new ServiceError('binary_file', 'File is not UTF-8 text');
|
|
101
|
+
const lines = text.split('\n'),
|
|
102
|
+
page: { line: number; text: string }[] = [];
|
|
103
|
+
let used = 0,
|
|
104
|
+
next = offset;
|
|
105
|
+
while (next < lines.length && page.length < 100) {
|
|
106
|
+
const line = lines[next]!;
|
|
107
|
+
if (Buffer.byteLength(line) > 4096)
|
|
108
|
+
throw new ServiceError('line_limit', 'Line exceeds text limit');
|
|
109
|
+
if ((used += Buffer.byteLength(line)) > 8192) break;
|
|
110
|
+
page.push({ line: next + 1, text: line });
|
|
111
|
+
next++;
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
...identity,
|
|
115
|
+
path,
|
|
116
|
+
hash: digest(file),
|
|
117
|
+
lines: page,
|
|
118
|
+
next_offset: next < lines.length ? next : null,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
if (action === 'search') {
|
|
122
|
+
const query = string(args.query, 'query', 256),
|
|
123
|
+
matches: unknown[] = [];
|
|
124
|
+
let index = 0,
|
|
125
|
+
next: number | null = null;
|
|
126
|
+
outer: for (const path of Object.keys(tree).sort()) {
|
|
127
|
+
const bytes = Buffer.from(tree[path]!.content, 'base64'),
|
|
128
|
+
text = bytes.toString('utf8');
|
|
129
|
+
if (text.includes('\0') || !Buffer.from(text).equals(bytes)) continue;
|
|
130
|
+
const lines = text.split('\n');
|
|
131
|
+
for (let line = 0; line < lines.length; line++)
|
|
132
|
+
if (lines[line]!.includes(query)) {
|
|
133
|
+
if (index++ < offset) continue;
|
|
134
|
+
if (matches.length === 10) {
|
|
135
|
+
next = index - 1;
|
|
136
|
+
break outer;
|
|
137
|
+
}
|
|
138
|
+
matches.push({
|
|
139
|
+
path,
|
|
140
|
+
line: line + 1,
|
|
141
|
+
text: lines[line]!.slice(0, 512),
|
|
142
|
+
hash: digest(tree[path]),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return { ...identity, matches, next_offset: next };
|
|
147
|
+
}
|
|
148
|
+
if (action === 'diff' && binding.profile.role === 'reviewer') {
|
|
149
|
+
const base = JSON.parse(
|
|
150
|
+
this.artifacts.read(binding.repository!.base_artifact),
|
|
151
|
+
) as WorkspaceTree;
|
|
152
|
+
const paths = [...new Set([...Object.keys(base), ...Object.keys(tree)])]
|
|
153
|
+
.sort()
|
|
154
|
+
.filter((path) => digest(base[path] ?? null) !== digest(tree[path] ?? null));
|
|
155
|
+
return {
|
|
156
|
+
...identity,
|
|
157
|
+
changes: paths.slice(offset, offset + 5).map((path) => ({
|
|
158
|
+
path,
|
|
159
|
+
before_hash: base[path] ? digest(base[path]) : null,
|
|
160
|
+
after_hash: tree[path] ? digest(tree[path]) : null,
|
|
161
|
+
diff: workspaceDiff({
|
|
162
|
+
path,
|
|
163
|
+
before: base[path] ?? null,
|
|
164
|
+
after: tree[path] ?? null,
|
|
165
|
+
}).slice(0, 8192),
|
|
166
|
+
})),
|
|
167
|
+
next_offset: offset + 5 < paths.length ? offset + 5 : null,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
throw new ServiceError(
|
|
171
|
+
'authority_denied',
|
|
172
|
+
'Specialists can only inspect, list, search or read their snapshot; reviewers can inspect its diff',
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
report(binding: WorkerBinding, raw: unknown): unknown {
|
|
176
|
+
if (!binding.tools.includes('salaam_findings'))
|
|
177
|
+
throw new ServiceError('authority_denied', 'No findings grant');
|
|
178
|
+
if (!Array.isArray(raw) || raw.length > 32)
|
|
179
|
+
throw new ServiceError('invalid_field', 'At most 32 findings');
|
|
180
|
+
const tree = this.tree(binding);
|
|
181
|
+
const findings = raw.map((item) => {
|
|
182
|
+
if (!item || typeof item !== 'object' || Array.isArray(item))
|
|
183
|
+
throw new ServiceError('invalid_field', 'Invalid finding');
|
|
184
|
+
const f = item as Record<string, unknown>,
|
|
185
|
+
path = repositoryPath(f.path),
|
|
186
|
+
file = tree[path];
|
|
187
|
+
if (!file)
|
|
188
|
+
throw new ServiceError(
|
|
189
|
+
'not_found',
|
|
190
|
+
'Finding file is absent from the reviewed snapshot',
|
|
191
|
+
404,
|
|
192
|
+
);
|
|
193
|
+
const line = integer(f.line, 'line'),
|
|
194
|
+
end = integer(f.end_line ?? line, 'end_line'),
|
|
195
|
+
severity = string(f.severity, 'severity', 16);
|
|
196
|
+
const source = Buffer.from(file.content, 'base64').toString('utf8').split('\n');
|
|
197
|
+
if (
|
|
198
|
+
!['critical', 'high', 'medium', 'low'].includes(severity) ||
|
|
199
|
+
line < 1 ||
|
|
200
|
+
end < line ||
|
|
201
|
+
end >= line + 20 ||
|
|
202
|
+
end > source.length
|
|
203
|
+
)
|
|
204
|
+
throw new ServiceError('invalid_field', 'Invalid finding location or severity');
|
|
205
|
+
return {
|
|
206
|
+
path,
|
|
207
|
+
line,
|
|
208
|
+
end_line: end,
|
|
209
|
+
severity,
|
|
210
|
+
message: string(f.message, 'message', 2048),
|
|
211
|
+
hash: digest(file),
|
|
212
|
+
evidence: source
|
|
213
|
+
.slice(line - 1, end)
|
|
214
|
+
.join('\n')
|
|
215
|
+
.slice(0, 8192),
|
|
216
|
+
};
|
|
217
|
+
});
|
|
218
|
+
const input = digest(findings);
|
|
219
|
+
return this.store.transaction(() => {
|
|
220
|
+
const prior = this.store.db
|
|
221
|
+
.prepare('SELECT * FROM specialist_reports WHERE worker_id=?')
|
|
222
|
+
.get(binding.worker_id);
|
|
223
|
+
if (prior) {
|
|
224
|
+
if (prior.input_digest !== input)
|
|
225
|
+
throw new ServiceError(
|
|
226
|
+
'request_conflict',
|
|
227
|
+
'Final findings already bind different content',
|
|
228
|
+
);
|
|
229
|
+
return {
|
|
230
|
+
artifact_id: String(prior.artifact_id),
|
|
231
|
+
kind: 'analysis',
|
|
232
|
+
verified_success: false,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
const artifact = this.artifacts.prepare(
|
|
236
|
+
binding.session_id,
|
|
237
|
+
JSON.stringify({
|
|
238
|
+
schema_version: 1,
|
|
239
|
+
worker_id: binding.worker_id,
|
|
240
|
+
attempt_id: binding.attempt_id,
|
|
241
|
+
binding_digest: digest(binding),
|
|
242
|
+
snapshot_digest: binding.repository!.digest,
|
|
243
|
+
kind: 'analysis',
|
|
244
|
+
findings,
|
|
245
|
+
}),
|
|
246
|
+
);
|
|
247
|
+
artifact.commit();
|
|
248
|
+
this.store.db
|
|
249
|
+
.prepare('INSERT INTO specialist_reports VALUES (?,?,?)')
|
|
250
|
+
.run(binding.worker_id, input, artifact.id);
|
|
251
|
+
this.store.event(binding.owner_session, 'specialist.findings', {
|
|
252
|
+
worker_id: binding.worker_id,
|
|
253
|
+
artifact_id: artifact.id,
|
|
254
|
+
kind: 'analysis',
|
|
255
|
+
});
|
|
256
|
+
return { artifact_id: artifact.id, kind: 'analysis', verified_success: false };
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|