@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/tui-view.js
CHANGED
|
@@ -9,7 +9,8 @@ export function modelLabel(client) {
|
|
|
9
9
|
return 'Connect your workspace';
|
|
10
10
|
if (['offline', 'salaam-proof'].includes(route.provider))
|
|
11
11
|
return 'Offline demo';
|
|
12
|
-
|
|
12
|
+
const warning = client.health?.provider_capabilities?.output_token_cap === false ? ' · no output cap' : '';
|
|
13
|
+
return (route.model.replace('claude-sonnet-', 'Claude Sonnet ').replace(/(\d)-(\d)$/, '$1.$2') + warning);
|
|
13
14
|
}
|
|
14
15
|
export class Dynamic {
|
|
15
16
|
content;
|
|
@@ -305,6 +306,42 @@ export class DocumentView {
|
|
|
305
306
|
? 'Occurrence ' + job.occurrence.id + ' · ' + job.occurrence.occurrence_key
|
|
306
307
|
: 'No occurrence admitted', width), ...this.text(job.reason, width));
|
|
307
308
|
}
|
|
309
|
+
const team = c.snapshot.team;
|
|
310
|
+
if (team?.assignments.length) {
|
|
311
|
+
lines.push(...this.title('TEAM BOARD', width));
|
|
312
|
+
for (const a of team.assignments) {
|
|
313
|
+
lines.push(...this.text(`${shortId(a.id)} · ${a.profile_id} · claim ${a.generation} · ${a.step_state}${a.stale ? ' · stale plan' : ''}`, width));
|
|
314
|
+
if (a.runtime)
|
|
315
|
+
lines.push(...this.text(a.runtime.id + ' · invocation budget · model usage unknown', width));
|
|
316
|
+
if (a.execution?.peer)
|
|
317
|
+
lines.push(...this.text('Peer ' +
|
|
318
|
+
a.execution.peer.peer_id +
|
|
319
|
+
' · task ' +
|
|
320
|
+
(a.execution.peer.task_id ?? 'acceptance unknown'), width), ...this.text((a.execution.peer.remote_state ?? 'remote state unknown') +
|
|
321
|
+
' · ' +
|
|
322
|
+
(a.execution.peer.cancel_state ?? 'no cancel requested'), width));
|
|
323
|
+
if (a.runtime?.provider_route)
|
|
324
|
+
lines.push(...this.text('Backend ' +
|
|
325
|
+
a.runtime.provider_route.provider +
|
|
326
|
+
'/' +
|
|
327
|
+
a.runtime.provider_route.model, width));
|
|
328
|
+
if (a.execution)
|
|
329
|
+
lines.push(...this.text(a.execution.state +
|
|
330
|
+
' · ' +
|
|
331
|
+
(a.execution.stop_scope === 'never-dispatched'
|
|
332
|
+
? 'not dispatched'
|
|
333
|
+
: a.execution.stop_scope === 'remote-terminal-receipt'
|
|
334
|
+
? a.execution.stopped
|
|
335
|
+
? 'remote terminal receipt'
|
|
336
|
+
: 'remote stop unconfirmed'
|
|
337
|
+
: a.execution.stopped
|
|
338
|
+
? 'stop confirmed'
|
|
339
|
+
: 'stop unconfirmed'), width));
|
|
340
|
+
}
|
|
341
|
+
lines.push(...this.text('Messages are passive. Starting or continuing work requires a separate grant.', width));
|
|
342
|
+
for (const m of team.messages)
|
|
343
|
+
lines.push(...this.text(`${shortId(m.id)} · ${m.sender === 'operator' ? 'operator' : shortId(m.sender)} → ${shortId(m.recipient)} · ${m.acknowledged_at ? 'acknowledged' : m.delivered_at ? 'delivered' : 'stored'}`, width));
|
|
344
|
+
}
|
|
308
345
|
const task = c.snapshot.work_task;
|
|
309
346
|
if (task) {
|
|
310
347
|
lines.push(...this.title('DURABLE TASK · ' + task.state.toUpperCase(), width), ...this.text(task.id + ' · revision ' + task.revision + ' · plan ' + task.plan_revision, width), ...this.text(task.objective, width), ...this.text(task.reason, width));
|
|
@@ -314,18 +351,30 @@ export class DocumentView {
|
|
|
314
351
|
' spent + ' +
|
|
315
352
|
task.runtime.reserved_requests +
|
|
316
353
|
' reserved / ' +
|
|
317
|
-
task.limits.requests, width), ...this.text(
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
354
|
+
task.limits.requests, width), ...this.text(task.limits.token_units === null
|
|
355
|
+
? 'Token budget disabled · ' +
|
|
356
|
+
(task.limits.output_token_bound === null
|
|
357
|
+
? 'no task output-token bound'
|
|
358
|
+
: 'output bound ' + task.limits.output_token_bound)
|
|
359
|
+
: 'Declared token units ' +
|
|
360
|
+
task.runtime.token_units +
|
|
361
|
+
' + ' +
|
|
362
|
+
task.runtime.reserved_tokens +
|
|
363
|
+
' reserved / ' +
|
|
364
|
+
task.limits.token_units, width), ...this.text('Bounded progression ' +
|
|
323
365
|
(task.runtime.auto_enabled
|
|
324
366
|
? 'enabled · ' + task.runtime.remaining_steps + ' new steps remain'
|
|
325
367
|
: 'paused') +
|
|
326
368
|
(task.runtime.auto_error ? ' · ' + task.runtime.auto_error : ''), width));
|
|
327
|
-
if (task.verification)
|
|
328
|
-
|
|
369
|
+
if (task.verification) {
|
|
370
|
+
const verification = task.verification;
|
|
371
|
+
lines.push(...this.title('VERIFICATION', width));
|
|
372
|
+
for (const check of verification.checks ?? [])
|
|
373
|
+
lines.push(...this.text(`${check.check_key} · ${check.state} · ${check.fresh ? 'current' : 'historical'} · r${check.workspace_revision} · ${shortId(check.id)}`, width));
|
|
374
|
+
if (verification.completion)
|
|
375
|
+
lines.push(...this.text(`Task ${verification.completion.state} · ${verification.completion.fresh ? 'current verification' : 'verification needs refresh'}`, width));
|
|
376
|
+
lines.push(...this.text('Ctrl+K → Inspect task for complete check receipts.', width));
|
|
377
|
+
}
|
|
329
378
|
for (const constraint of task.constraints)
|
|
330
379
|
lines.push(...this.text('Constraint: ' + constraint, width));
|
|
331
380
|
for (const step of task.steps)
|
|
@@ -339,6 +388,8 @@ export class DocumentView {
|
|
|
339
388
|
for (const fact of facts)
|
|
340
389
|
lines.push(...this.title(fact.kind.toUpperCase(), width, 'muted'), ...this.prose(fact.id, fact.text, width - 4).map((line) => t.line(line, width)), t.fill('', width));
|
|
341
390
|
lines.push(...this.title('CHILD WORK', width));
|
|
391
|
+
for (const agent of c.snapshot.agents ?? [])
|
|
392
|
+
lines.push(...this.text(`${agent.profile_id} · revision ${agent.revision} · ${agent.state} · ${agent.route.provider}/${agent.route.model} · ${agent.tools.join(', ')}`, width));
|
|
342
393
|
if (!c.snapshot.children.length)
|
|
343
394
|
lines.push(...this.text('No child work in this session.', width));
|
|
344
395
|
for (const child of c.snapshot.children)
|
|
@@ -347,7 +398,11 @@ export class DocumentView {
|
|
|
347
398
|
' · ' +
|
|
348
399
|
child.state +
|
|
349
400
|
(child.result_id ? ' · result ' + shortId(child.result_id) : '') +
|
|
350
|
-
(child.acknowledged ? ' · acknowledged' : ''), width)
|
|
401
|
+
(child.acknowledged ? ' · acknowledged' : ''), width), ...(child.worker
|
|
402
|
+
? this.text(`${child.worker.binding.profile.id} · ${child.worker.binding.runtime} · generation ${child.worker.generation} · ${child.worker.dispatch_state} · ${child.worker.binding.bounds.scope === 'model-requests' ? child.worker.binding.bounds.requests + ' model requests' : child.worker.binding.bounds.invocations + ' invocation · internal model usage unknown'}`, width)
|
|
403
|
+
: []), ...(child.worker?.binding.isolation
|
|
404
|
+
? this.text(`Private draft ${shortId(child.worker.binding.isolation.workspace_id)} · execution ${child.worker.binding.isolation.execution.evidence} · root integration requires a reviewed proposal digest`, width)
|
|
405
|
+
: []));
|
|
351
406
|
lines.push(t.fill('', width), ...this.title('USAGE', width));
|
|
352
407
|
if (c.usage)
|
|
353
408
|
lines.push(...this.text(`${c.usage.observations} provider observations · ${c.usage.unknown_observations} unknown`, width), ...this.text(`Recorded tokens: ${c.usage.known_input} input · ${c.usage.known_output} output`, width), ...this.text(`Recorded cost $${c.usage.known_cost.toFixed(4)} · estimated $${c.usage.estimated_cost.toFixed(4)}`, width));
|
package/dist/src/tui.js
CHANGED
|
@@ -337,7 +337,9 @@ export class SalaamApp {
|
|
|
337
337
|
await this.client.list();
|
|
338
338
|
if (this.client.connection === 'connected')
|
|
339
339
|
return;
|
|
340
|
-
const provider = existsSync(credentialPath())
|
|
340
|
+
const provider = existsSync(credentialPath()) || existsSync(credentialPath() + '.json')
|
|
341
|
+
? 'anthropic'
|
|
342
|
+
: 'offline';
|
|
341
343
|
await promisify(execFile)(process.execPath, [
|
|
342
344
|
join(ROOT, 'bin/salaam.mjs'),
|
|
343
345
|
'service',
|
|
@@ -627,7 +629,7 @@ export class SalaamApp {
|
|
|
627
629
|
else if (action === 'auto')
|
|
628
630
|
this.taskInput('Bounded progression', String(task.steps.filter((s) => s.state === 'pending').length), 'Maximum new steps (1–32)', 'Steps advance only after their configured checks pass. Human approvals remain separate.', (value) => {
|
|
629
631
|
const max_steps = Number(value);
|
|
630
|
-
this.confirm('Run this bounded plan', `Task ${shortId(task.id)} · plan revision ${task.plan_revision}\nAt most ${max_steps} new steps; each step needs fresh passing checks.\nLimits: ${task.limits.requests} requests · ${task.limits.runtime_ms} ms · ${task.limits.concurrency} concurrent executions.\n${task.limits.token_units
|
|
632
|
+
this.confirm('Run this bounded plan', `Task ${shortId(task.id)} · plan revision ${task.plan_revision}\nAt most ${max_steps} new steps; each step needs fresh passing checks.\nLimits: ${task.limits.requests} requests · ${task.limits.runtime_ms} ms · ${task.limits.concurrency} concurrent executions.\n${task.limits.token_units === null ? 'Token budget disabled' : task.limits.token_units + ' declared token units'} · ${task.limits.verification_runs} verification runs.\nChecks: ${task.acceptance.map((c) => c.key).join(', ') || 'none'} · Deliverables: ${task.deliverables.length}.\nDeterministic compaction at idle boundaries. Human approvals remain separate.`, 'Run bounded task', () => this.client.taskAction('auto', { max_steps, compact: true }, reviewed));
|
|
631
633
|
});
|
|
632
634
|
else if (action === 'pause')
|
|
633
635
|
void this.execute(() => this.client.taskAction('auto', { max_steps: 0, compact: false }, reviewed));
|
package/dist/src/usage.js
CHANGED
|
@@ -80,6 +80,24 @@ export class UsageLedger {
|
|
|
80
80
|
const key = typeof m?.responseId === 'string' && m.responseId.startsWith('salaam-request:')
|
|
81
81
|
? m.responseId
|
|
82
82
|
: `pi:${session}:${entry.id}`;
|
|
83
|
+
// Managed live streams report receipts through the authenticated bridge.
|
|
84
|
+
// Pi normalizes a missing receipt to zeros; importing that transcript must
|
|
85
|
+
// never upgrade a conservatively unknown observation to a zero charge.
|
|
86
|
+
if (key.startsWith('salaam-request:')) {
|
|
87
|
+
const managed = this.store.db
|
|
88
|
+
.prepare('SELECT session_id,provider FROM usage_observations WHERE source_key=?')
|
|
89
|
+
.get(key);
|
|
90
|
+
if (managed && managed.provider !== 'salaam-proof') {
|
|
91
|
+
if (managed.session_id !== session)
|
|
92
|
+
throw new ServiceError('invalid_usage', 'Usage does not belong to this session');
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
// A rejected live request can still produce a Pi error transcript. Its
|
|
96
|
+
// absence from admission is definitive: no request was dispatched.
|
|
97
|
+
const route = this.store.providerRoute(session);
|
|
98
|
+
if (!managed && route && route.provider !== 'salaam-proof')
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
83
101
|
// An aborted provider message may carry placeholder zeros. They are not evidence of zero spend.
|
|
84
102
|
const complete = m && !['error', 'aborted'].includes(String(m.stopReason));
|
|
85
103
|
if ((complete && m.usage) || entry.usage)
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { canonical, digest, textDigest, ServiceError } from './protocol.js';
|
|
3
|
+
import { POLICY_DIGEST } from './config.js';
|
|
4
|
+
/** Local contract v1. These are enforced capabilities, not upstream feature claims. */
|
|
5
|
+
export const NATIVE_CAPABILITIES = {
|
|
6
|
+
schema_version: 1,
|
|
7
|
+
runtime: 'native-pi',
|
|
8
|
+
model_requests: true,
|
|
9
|
+
output_cap: 'authentication-dependent',
|
|
10
|
+
usage_receipts: true,
|
|
11
|
+
resume_dispatch: false,
|
|
12
|
+
confirmed_stop: false,
|
|
13
|
+
files: false,
|
|
14
|
+
commands: false,
|
|
15
|
+
isolation: 'managed-process-no-coding-authority',
|
|
16
|
+
retention: 'durable-local',
|
|
17
|
+
};
|
|
18
|
+
export const DIAGNOSTIC_PROFILE = {
|
|
19
|
+
schema_version: 1,
|
|
20
|
+
id: 'salaam/diagnostic',
|
|
21
|
+
revision: 1,
|
|
22
|
+
tools: ['salaam_probe'],
|
|
23
|
+
max_depth: 1,
|
|
24
|
+
};
|
|
25
|
+
export function nativeCapabilities(profile, coordination = false, isolation) {
|
|
26
|
+
const caps = profile.role
|
|
27
|
+
? {
|
|
28
|
+
...NATIVE_CAPABILITIES,
|
|
29
|
+
repository_read: profile.tools.includes('salaam_repository'),
|
|
30
|
+
findings: profile.tools.includes('salaam_findings'),
|
|
31
|
+
}
|
|
32
|
+
: NATIVE_CAPABILITIES;
|
|
33
|
+
return {
|
|
34
|
+
...caps,
|
|
35
|
+
...(coordination ? { team_messages: true } : {}),
|
|
36
|
+
...(isolation
|
|
37
|
+
? {
|
|
38
|
+
files: profile.tools.includes('salaam_files'),
|
|
39
|
+
commands: profile.tools.includes('salaam_command'),
|
|
40
|
+
isolation: isolation.execution.evidence === 'fixture'
|
|
41
|
+
? 'simulated-owned-copy'
|
|
42
|
+
: 'owned-copy-and-proven-executor',
|
|
43
|
+
execution_profile: isolation.execution.profile_digest,
|
|
44
|
+
execution_engine: isolation.execution.engine_id,
|
|
45
|
+
}
|
|
46
|
+
: {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function externalCapabilities(runtime, tools) {
|
|
50
|
+
return {
|
|
51
|
+
schema_version: 1,
|
|
52
|
+
runtime: runtime.kind,
|
|
53
|
+
model_requests: false,
|
|
54
|
+
output_cap: runtime.output_cap,
|
|
55
|
+
files: runtime.files && tools.includes('salaam_files'),
|
|
56
|
+
commands: runtime.commands && tools.includes('salaam_command'),
|
|
57
|
+
model_usage: 'unknown',
|
|
58
|
+
isolation: runtime.kind === 'a2a'
|
|
59
|
+
? 'remote-peer-no-local-tool-authority'
|
|
60
|
+
: runtime.evidence === 'fixture'
|
|
61
|
+
? 'simulated-whole-runtime'
|
|
62
|
+
: 'proven-whole-runtime',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** Never equate one opaque invocation with one model request. Invocation admission is separate. */
|
|
66
|
+
export function assertModelRequestBoundary(capabilities) {
|
|
67
|
+
if (capabilities.schema_version !== 1)
|
|
68
|
+
throw new ServiceError('worker_capability_version', 'Unsupported worker capability version');
|
|
69
|
+
if (capabilities.model_requests !== true)
|
|
70
|
+
throw new ServiceError('worker_budget_scope', 'Model-request bounds require enforced admission; opaque workers need explicit invocation bounds');
|
|
71
|
+
}
|
|
72
|
+
export class WorkerBoundary {
|
|
73
|
+
store;
|
|
74
|
+
profiles;
|
|
75
|
+
assertAssignment;
|
|
76
|
+
assertIsolation;
|
|
77
|
+
constructor(store) {
|
|
78
|
+
this.store = store;
|
|
79
|
+
}
|
|
80
|
+
/** Called within the schema migration transaction. Cached responses and child rows stay intact. */
|
|
81
|
+
migrate() {
|
|
82
|
+
this.store.db.exec(`
|
|
83
|
+
CREATE TABLE worker_profiles(id TEXT NOT NULL,revision INTEGER NOT NULL,digest TEXT NOT NULL,document TEXT NOT NULL,revoked INTEGER NOT NULL DEFAULT 0,PRIMARY KEY(id,revision));
|
|
84
|
+
CREATE TABLE child_workers(child_id TEXT PRIMARY KEY REFERENCES child_tasks(id),binding TEXT NOT NULL,binding_digest TEXT NOT NULL,generation INTEGER NOT NULL DEFAULT 0,worker_epoch INTEGER,dispatch_state TEXT NOT NULL);
|
|
85
|
+
CREATE UNIQUE INDEX child_worker_identity ON child_workers(json_extract(binding,'$.worker_id'));
|
|
86
|
+
`);
|
|
87
|
+
for (const child of this.store.db
|
|
88
|
+
.prepare('SELECT * FROM child_tasks')
|
|
89
|
+
.all()) {
|
|
90
|
+
this.freeze(child, null, true);
|
|
91
|
+
this.store.db
|
|
92
|
+
.prepare('UPDATE child_workers SET dispatch_state=? WHERE child_id=?')
|
|
93
|
+
.run(child.state === 'accepted' ? 'interrupted' : 'terminal', child.id);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** Caller owns the transaction that creates the child, its reservation and input artifact. */
|
|
97
|
+
freeze(child, inputArtifact, legacy = false, agent, repository, assignment, isolation, external) {
|
|
98
|
+
const admittedProfile = agent?.profile ?? DIAGNOSTIC_PROFILE;
|
|
99
|
+
const profileDigest = digest(admittedProfile);
|
|
100
|
+
this.store.db
|
|
101
|
+
.prepare('INSERT OR IGNORE INTO worker_profiles(id,revision,digest,document) VALUES (?,?,?,?)')
|
|
102
|
+
.run(admittedProfile.id, admittedProfile.revision, profileDigest, canonical(admittedProfile));
|
|
103
|
+
const profile = this.store.db
|
|
104
|
+
.prepare('SELECT digest,revoked FROM worker_profiles WHERE id=? AND revision=?')
|
|
105
|
+
.get(admittedProfile.id, admittedProfile.revision);
|
|
106
|
+
if (profile?.revoked || profile?.digest !== profileDigest)
|
|
107
|
+
throw new ServiceError('worker_profile_revoked', 'Diagnostic worker profile is revoked or changed');
|
|
108
|
+
const task = this.store.db
|
|
109
|
+
.prepare('SELECT id FROM durable_tasks WHERE session_id=?')
|
|
110
|
+
.get(child.parent_session);
|
|
111
|
+
const workspace = this.store.db
|
|
112
|
+
.prepare('SELECT id,revision,digest FROM workspaces WHERE session_id=?')
|
|
113
|
+
.get(child.parent_session);
|
|
114
|
+
const binding = {
|
|
115
|
+
schema_version: 1,
|
|
116
|
+
worker_id: randomUUID(),
|
|
117
|
+
runtime: external?.kind ?? 'native-pi',
|
|
118
|
+
owner_session: child.parent_session,
|
|
119
|
+
owner_task: task ? String(task.id) : null,
|
|
120
|
+
parent_attempt: child.parent_attempt,
|
|
121
|
+
step_key: child.step_key,
|
|
122
|
+
session_id: child.child_session,
|
|
123
|
+
attempt_id: child.child_attempt,
|
|
124
|
+
input_digest: child.input_digest,
|
|
125
|
+
input_artifact: inputArtifact,
|
|
126
|
+
profile: admittedProfile,
|
|
127
|
+
profile_digest: profileDigest,
|
|
128
|
+
capabilities: nativeCapabilities(admittedProfile, !!assignment, isolation),
|
|
129
|
+
capability_digest: digest(nativeCapabilities(admittedProfile, !!assignment, isolation)),
|
|
130
|
+
tools: JSON.parse(child.authority),
|
|
131
|
+
route: external
|
|
132
|
+
? null
|
|
133
|
+
: (agent?.route ??
|
|
134
|
+
this.store.providerRoute(child.child_session) ??
|
|
135
|
+
this.store.providerRoute(child.parent_session)),
|
|
136
|
+
workspace: workspace
|
|
137
|
+
? {
|
|
138
|
+
id: String(workspace.id),
|
|
139
|
+
revision: Number(workspace.revision),
|
|
140
|
+
digest: String(workspace.digest),
|
|
141
|
+
}
|
|
142
|
+
: null,
|
|
143
|
+
policy_digest: POLICY_DIGEST,
|
|
144
|
+
bounds: external
|
|
145
|
+
? { scope: 'invocations', invocations: 1 }
|
|
146
|
+
: { scope: 'model-requests', requests: child.budget },
|
|
147
|
+
...(external ? { external } : {}),
|
|
148
|
+
legacy,
|
|
149
|
+
...(agent ? { agent } : {}),
|
|
150
|
+
...(repository ? { repository } : {}),
|
|
151
|
+
...(assignment ? { assignment } : {}),
|
|
152
|
+
...(isolation ? { isolation } : {}),
|
|
153
|
+
};
|
|
154
|
+
if (external) {
|
|
155
|
+
binding.capabilities = externalCapabilities(external, binding.tools);
|
|
156
|
+
binding.capability_digest = digest(binding.capabilities);
|
|
157
|
+
}
|
|
158
|
+
else
|
|
159
|
+
assertModelRequestBoundary(binding.capabilities);
|
|
160
|
+
this.store.db
|
|
161
|
+
.prepare("INSERT INTO child_workers(child_id,binding,binding_digest,dispatch_state) VALUES (?,?,?,'intent')")
|
|
162
|
+
.run(child.id, canonical(binding), digest(binding));
|
|
163
|
+
// Migration has no event publication; the child itself remains the legacy receipt.
|
|
164
|
+
if (!legacy)
|
|
165
|
+
this.store.event(child.parent_session, 'child.worker_intent', {
|
|
166
|
+
child_id: child.id,
|
|
167
|
+
worker_id: binding.worker_id,
|
|
168
|
+
binding_digest: digest(binding),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
view(childId) {
|
|
172
|
+
const row = this.store.db.prepare('SELECT * FROM child_workers WHERE child_id=?').get(childId);
|
|
173
|
+
if (!row)
|
|
174
|
+
throw new ServiceError('worker_binding_missing', 'Child has no worker binding');
|
|
175
|
+
return {
|
|
176
|
+
binding: JSON.parse(String(row.binding)),
|
|
177
|
+
binding_digest: String(row.binding_digest),
|
|
178
|
+
generation: Number(row.generation),
|
|
179
|
+
worker_epoch: row.worker_epoch === null ? null : Number(row.worker_epoch),
|
|
180
|
+
dispatch_state: row.dispatch_state,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
validate(child, route) {
|
|
184
|
+
const view = this.view(child.id), b = view.binding;
|
|
185
|
+
if (b.agent) {
|
|
186
|
+
if (!this.profiles ||
|
|
187
|
+
b.agent.owner_session !== child.parent_session ||
|
|
188
|
+
b.agent.profile_digest !== b.profile_digest ||
|
|
189
|
+
canonical(b.agent.route) !== canonical(b.route))
|
|
190
|
+
throw new ServiceError('worker_binding_changed', 'Specialist admission belongs to another owner or route');
|
|
191
|
+
this.profiles.validate(b.agent);
|
|
192
|
+
}
|
|
193
|
+
const profile = this.store.db
|
|
194
|
+
.prepare('SELECT * FROM worker_profiles WHERE id=? AND revision=?')
|
|
195
|
+
.get(b.profile.id, b.profile.revision);
|
|
196
|
+
if (b.tools.some((t) => ['salaam_files', 'salaam_command'].includes(t)) && !b.isolation)
|
|
197
|
+
throw new ServiceError('worker_binding_changed', 'Coding workers require isolated workspace ownership');
|
|
198
|
+
if (b.isolation) {
|
|
199
|
+
if (!this.assertIsolation)
|
|
200
|
+
throw new ServiceError('worker_binding_changed', 'Execution boundary is unavailable');
|
|
201
|
+
this.assertIsolation(b);
|
|
202
|
+
}
|
|
203
|
+
if (b.assignment) {
|
|
204
|
+
if (!this.assertAssignment)
|
|
205
|
+
throw new ServiceError('worker_binding_changed', 'Assignment owner is unavailable');
|
|
206
|
+
this.assertAssignment(b);
|
|
207
|
+
}
|
|
208
|
+
const task = this.store.db
|
|
209
|
+
.prepare('SELECT id FROM durable_tasks WHERE session_id=?')
|
|
210
|
+
.get(child.parent_session);
|
|
211
|
+
const input = this.store.db
|
|
212
|
+
.prepare('SELECT s.text,a.digest,a.session_id FROM submissions s JOIN artifacts a ON a.id=? WHERE s.attempt_id=?')
|
|
213
|
+
.get(b.input_artifact, child.child_attempt);
|
|
214
|
+
if (b.schema_version !== 1 ||
|
|
215
|
+
digest(b) !== view.binding_digest ||
|
|
216
|
+
b.runtime !== 'native-pi' ||
|
|
217
|
+
b.owner_session !== child.parent_session ||
|
|
218
|
+
b.owner_task !== (task ? String(task.id) : null) ||
|
|
219
|
+
b.parent_attempt !== child.parent_attempt ||
|
|
220
|
+
b.session_id !== child.child_session ||
|
|
221
|
+
b.attempt_id !== child.child_attempt ||
|
|
222
|
+
b.step_key !== child.step_key ||
|
|
223
|
+
b.input_digest !== child.input_digest ||
|
|
224
|
+
!input ||
|
|
225
|
+
input.session_id !== child.child_session ||
|
|
226
|
+
input.digest !== textDigest(String(input.text)) ||
|
|
227
|
+
b.bounds.scope !== 'model-requests' ||
|
|
228
|
+
b.bounds.requests !== child.budget ||
|
|
229
|
+
canonical(b.tools) !== child.authority ||
|
|
230
|
+
b.tools.some((tool) => !b.profile.tools.includes(tool) && !(tool === 'salaam_team' && b.assignment)) ||
|
|
231
|
+
b.policy_digest !== POLICY_DIGEST ||
|
|
232
|
+
canonical(b.route) !== canonical(route) ||
|
|
233
|
+
b.capability_digest !== digest(nativeCapabilities(b.profile, !!b.assignment, b.isolation)) ||
|
|
234
|
+
digest(b.capabilities) !== b.capability_digest ||
|
|
235
|
+
(!b.agent && b.profile_digest !== digest(DIAGNOSTIC_PROFILE)) ||
|
|
236
|
+
digest(b.profile) !== b.profile_digest ||
|
|
237
|
+
!profile ||
|
|
238
|
+
profile.revoked ||
|
|
239
|
+
profile.digest !== b.profile_digest ||
|
|
240
|
+
digest(JSON.parse(String(profile.document))) !== b.profile_digest)
|
|
241
|
+
throw new ServiceError('worker_binding_changed', 'Frozen worker owner, profile, capabilities or configuration changed');
|
|
242
|
+
assertModelRequestBoundary(b.capabilities);
|
|
243
|
+
return view;
|
|
244
|
+
}
|
|
245
|
+
/** Commit dispatch intent before launching a process. It can be claimed only once. */
|
|
246
|
+
claim(child, epoch, route) {
|
|
247
|
+
this.store.transaction(() => {
|
|
248
|
+
const view = this.validate(child, route);
|
|
249
|
+
if (view.dispatch_state !== 'intent' ||
|
|
250
|
+
child.state !== 'accepted' ||
|
|
251
|
+
this.store.cancelled(child.child_attempt))
|
|
252
|
+
throw new ServiceError('worker_already_dispatched', 'Child worker dispatch cannot be replayed');
|
|
253
|
+
this.store.assertEpoch(child.child_session, epoch);
|
|
254
|
+
this.store.db
|
|
255
|
+
.prepare("UPDATE child_workers SET generation=generation+1,worker_epoch=?,dispatch_state='dispatching' WHERE child_id=?")
|
|
256
|
+
.run(epoch, child.id);
|
|
257
|
+
this.store.event(child.parent_session, 'child.worker_dispatching', {
|
|
258
|
+
child_id: child.id,
|
|
259
|
+
worker_id: view.binding.worker_id,
|
|
260
|
+
generation: view.generation + 1,
|
|
261
|
+
worker_epoch: epoch,
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
assert(child, epoch, route) {
|
|
266
|
+
const view = this.validate(child, route);
|
|
267
|
+
this.store.assertEpoch(child.child_session, epoch);
|
|
268
|
+
if (view.generation !== 1 ||
|
|
269
|
+
view.worker_epoch !== epoch ||
|
|
270
|
+
!['dispatching', 'accepted'].includes(view.dispatch_state) ||
|
|
271
|
+
child.state !== 'accepted' ||
|
|
272
|
+
this.store.cancelled(child.child_attempt))
|
|
273
|
+
throw new ServiceError('stale_worker', 'Worker generation no longer owns this child');
|
|
274
|
+
return view;
|
|
275
|
+
}
|
|
276
|
+
accepted(child, epoch, route) {
|
|
277
|
+
this.store.transaction(() => {
|
|
278
|
+
const view = this.assert(child, epoch, route);
|
|
279
|
+
if (view.dispatch_state !== 'dispatching')
|
|
280
|
+
throw new ServiceError('worker_already_dispatched', 'Duplicate worker acceptance');
|
|
281
|
+
this.store.db
|
|
282
|
+
.prepare("UPDATE child_workers SET dispatch_state='accepted' WHERE child_id=?")
|
|
283
|
+
.run(child.id);
|
|
284
|
+
this.store.event(child.parent_session, 'child.worker_accepted', {
|
|
285
|
+
child_id: child.id,
|
|
286
|
+
worker_id: view.binding.worker_id,
|
|
287
|
+
generation: view.generation,
|
|
288
|
+
worker_epoch: epoch,
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
/** Called with the terminal child result in the same transaction. */
|
|
293
|
+
finish(child, interrupted) {
|
|
294
|
+
this.store.db
|
|
295
|
+
.prepare('UPDATE child_workers SET dispatch_state=? WHERE child_id=?')
|
|
296
|
+
.run(interrupted ? 'interrupted' : 'terminal', child.id);
|
|
297
|
+
}
|
|
298
|
+
}
|