@agilno-tech/rivet 0.1.0-alpha.0
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/LICENSE +202 -0
- package/NOTICE +6 -0
- package/README.md +59 -0
- package/bin/cli.js +42 -0
- package/dist/agents/backend-django-agent/SKILL.md +35 -0
- package/dist/agents/backend-nestjs-agent/SKILL.md +36 -0
- package/dist/agents/boss-agent/SKILL.md +28 -0
- package/dist/agents/bounded-worker/SKILL.md +28 -0
- package/dist/agents/data-performance-agent/SKILL.md +32 -0
- package/dist/agents/delivery-ticketing-agent/SKILL.md +36 -0
- package/dist/agents/devops-agent/SKILL.md +34 -0
- package/dist/agents/engineering-manager/SKILL.md +28 -0
- package/dist/agents/frontend-nextjs-agent/SKILL.md +34 -0
- package/dist/agents/frontend-web-agent/SKILL.md +31 -0
- package/dist/agents/incident-response-agent/SKILL.md +42 -0
- package/dist/agents/jest-agent/SKILL.md +77 -0
- package/dist/agents/mobile-agent/SKILL.md +32 -0
- package/dist/agents/playwright-agent/SKILL.md +54 -0
- package/dist/agents/product-design-manager/SKILL.md +27 -0
- package/dist/agents/qa-agent/SKILL.md +29 -0
- package/dist/agents/quality-manager/SKILL.md +28 -0
- package/dist/agents/vitest-agent/SKILL.md +77 -0
- package/dist/governance/pre-push-rules.md +75 -0
- package/dist/governance/prompt-hygiene.md +22 -0
- package/dist/governance/review-checklist.md +24 -0
- package/dist/governance/safety-and-data.md +27 -0
- package/dist/governance/usage-rules.md +21 -0
- package/dist/mandatory/address-pr-feedback/SKILL.md +196 -0
- package/dist/mandatory/agentic-goal/SKILL.md +42 -0
- package/dist/mandatory/agentic-status/SKILL.md +74 -0
- package/dist/mandatory/apply-design/SKILL.md +228 -0
- package/dist/mandatory/check-ac/SKILL.md +124 -0
- package/dist/mandatory/create-pr-and-commit/SKILL.md +304 -0
- package/dist/mandatory/design/SKILL.md +312 -0
- package/dist/mandatory/feature-workflow/SKILL.md +87 -0
- package/dist/mandatory/hotfix/SKILL.md +195 -0
- package/dist/mandatory/pre-push/SKILL.md +272 -0
- package/dist/mandatory/project-context/SKILL.md +180 -0
- package/dist/mandatory/release-docs/SKILL.md +191 -0
- package/dist/mandatory/review-pr/SKILL.md +323 -0
- package/dist/mandatory/security-review/SKILL.md +157 -0
- package/dist/skills/api-contract/SKILL.md +77 -0
- package/dist/skills/backend-django/SKILL.md +27 -0
- package/dist/skills/backend-nestjs/SKILL.md +65 -0
- package/dist/skills/bug-ticket-creation/SKILL.md +43 -0
- package/dist/skills/cloudwatch-troubleshooting/SKILL.md +73 -0
- package/dist/skills/database-migration/SKILL.md +97 -0
- package/dist/skills/debugging/SKILL.md +32 -0
- package/dist/skills/devops-infra/SKILL.md +26 -0
- package/dist/skills/documentation/SKILL.md +24 -0
- package/dist/skills/frontend-nextjs/SKILL.md +27 -0
- package/dist/skills/incident-postmortem/SKILL.md +38 -0
- package/dist/skills/kubernetes-troubleshooting/SKILL.md +71 -0
- package/dist/skills/mobile-react-native/SKILL.md +37 -0
- package/dist/skills/postgres-analytics/SKILL.md +29 -0
- package/dist/skills/product-jira-ticketing/SKILL.md +28 -0
- package/dist/skills/qa-bug-analysis/SKILL.md +27 -0
- package/dist/skills/refactoring/SKILL.md +32 -0
- package/dist/skills/sprint-planning/SKILL.md +32 -0
- package/dist/skills/testing-quality/SKILL.md +62 -0
- package/dist/v2/protocols/agent-orchestration.md +49 -0
- package/dist/v2/protocols/delivery.md +45 -0
- package/dist/v2/protocols/design-authority.md +29 -0
- package/dist/v2/protocols/goal-graph.md +44 -0
- package/dist/v2/protocols/pattern-first-development.md +29 -0
- package/dist/v2/protocols/qa-evidence.md +29 -0
- package/dist/v2/protocols/security.md +29 -0
- package/dist/v2/schemas/event.schema.json +86 -0
- package/dist/v2/schemas/evidence.schema.json +94 -0
- package/dist/v2/schemas/feature-decomposition.schema.json +48 -0
- package/dist/v2/schemas/feature-plan.schema.json +95 -0
- package/dist/v2/schemas/goal-graph.schema.json +61 -0
- package/dist/v2/schemas/integration.schema.json +119 -0
- package/dist/v2/schemas/orchestration.schema.json +69 -0
- package/dist/v2/schemas/project.schema.json +209 -0
- package/dist/v2/schemas/providers.schema.json +142 -0
- package/dist/v2/schemas/quality.schema.json +50 -0
- package/dist/v2/schemas/work-action.schema.json +44 -0
- package/dist/v2/schemas/work-request.schema.json +253 -0
- package/dist/v2/templates/evidence/qa-bundle.json +194 -0
- package/dist/v2/templates/github-actions/rivet-deploy.yml +98 -0
- package/dist/v2/templates/harness/SKILL.md +90 -0
- package/dist/v2/templates/project/.rivet/orchestration.yaml +42 -0
- package/dist/v2/templates/project/.rivet/project.yaml +14 -0
- package/dist/v2/templates/project/.rivet/providers.yaml +8 -0
- package/dist/v2/templates/project/.rivet/quality.yaml +16 -0
- package/package.json +58 -0
- package/protocols/agent-orchestration.md +49 -0
- package/protocols/delivery.md +45 -0
- package/protocols/design-authority.md +29 -0
- package/protocols/goal-graph.md +44 -0
- package/protocols/pattern-first-development.md +29 -0
- package/protocols/qa-evidence.md +29 -0
- package/protocols/security.md +29 -0
- package/schemas/event.schema.json +86 -0
- package/schemas/evidence.schema.json +94 -0
- package/schemas/feature-decomposition.schema.json +48 -0
- package/schemas/feature-plan.schema.json +95 -0
- package/schemas/goal-graph.schema.json +61 -0
- package/schemas/integration.schema.json +119 -0
- package/schemas/orchestration.schema.json +69 -0
- package/schemas/project.schema.json +209 -0
- package/schemas/providers.schema.json +142 -0
- package/schemas/quality.schema.json +50 -0
- package/schemas/work-action.schema.json +44 -0
- package/schemas/work-request.schema.json +253 -0
- package/src/adapters/confluence.js +138 -0
- package/src/adapters/contract.js +657 -0
- package/src/adapters/factory.js +122 -0
- package/src/adapters/figma.js +178 -0
- package/src/adapters/fixtures.js +183 -0
- package/src/adapters/github.js +314 -0
- package/src/adapters/http.js +704 -0
- package/src/adapters/jira.js +195 -0
- package/src/adapters/linear.js +171 -0
- package/src/adapters/node-transport.js +59 -0
- package/src/cli/integration-setup-prompt.js +32 -0
- package/src/cli/interrupt.js +25 -0
- package/src/cli/main.js +596 -0
- package/src/cli/output.js +174 -0
- package/src/cli/parse-args.js +246 -0
- package/src/cli/project-discovery.js +70 -0
- package/src/cli/task-confirmation.js +38 -0
- package/src/cli/task-presentation.js +22 -0
- package/src/clients/claude.js +214 -0
- package/src/clients/codex.js +180 -0
- package/src/clients/compatibility.js +23 -0
- package/src/clients/contract.js +224 -0
- package/src/clients/fake.js +170 -0
- package/src/clients/process-runner.js +657 -0
- package/src/clients/result-contract.js +78 -0
- package/src/commands/delivery-publish.js +80 -0
- package/src/commands/delivery-remote.js +230 -0
- package/src/commands/delivery-review-update.js +39 -0
- package/src/commands/delivery-tracker.js +94 -0
- package/src/commands/delivery-transition.js +79 -0
- package/src/commands/delivery.js +223 -0
- package/src/commands/dependency-approval.js +21 -0
- package/src/commands/doctor.js +191 -0
- package/src/commands/evidence.js +44 -0
- package/src/commands/feature.js +212 -0
- package/src/commands/goals.js +231 -0
- package/src/commands/human-run.js +208 -0
- package/src/commands/human-task.js +278 -0
- package/src/commands/init.js +848 -0
- package/src/commands/install.js +814 -0
- package/src/commands/integration-setup.js +65 -0
- package/src/commands/integrations.js +54 -0
- package/src/commands/models.js +158 -0
- package/src/commands/orchestrate.js +61 -0
- package/src/commands/preflight.js +125 -0
- package/src/commands/protocols.js +228 -0
- package/src/commands/repositories.js +77 -0
- package/src/commands/setup-remote.js +41 -0
- package/src/commands/setup.js +214 -0
- package/src/commands/status.js +224 -0
- package/src/commands/support.js +44 -0
- package/src/commands/task-completion.js +104 -0
- package/src/commands/uninstall.js +234 -0
- package/src/commands/verify.js +171 -0
- package/src/commands/work.js +220 -0
- package/src/commands/worktrees.js +52 -0
- package/src/config/command-readiness.js +218 -0
- package/src/config/commands.js +206 -0
- package/src/config/defaults.js +55 -0
- package/src/config/load.js +230 -0
- package/src/config/validate.js +560 -0
- package/src/delivery/bitbucket-review.js +123 -0
- package/src/delivery/branch-publication.js +23 -0
- package/src/delivery/contract.js +379 -0
- package/src/delivery/github-deployment.js +132 -0
- package/src/delivery/github-review.js +5 -0
- package/src/delivery/github.js +382 -0
- package/src/delivery/gitlab-review.js +5 -0
- package/src/delivery/gitlab.js +395 -0
- package/src/delivery/prepare.js +90 -0
- package/src/delivery/publication-process.js +33 -0
- package/src/delivery/publication-transport.js +112 -0
- package/src/delivery/review-request.js +184 -0
- package/src/delivery/review-update.js +74 -0
- package/src/delivery/service.js +400 -0
- package/src/delivery/store.js +76 -0
- package/src/delivery/tracker-target.js +51 -0
- package/src/delivery/tracker-transition.js +158 -0
- package/src/delivery/tracker.js +164 -0
- package/src/discovery/git.js +85 -0
- package/src/discovery/portable.js +108 -0
- package/src/discovery/project.js +345 -0
- package/src/discovery/tools.js +145 -0
- package/src/evaluations/approval.js +20 -0
- package/src/evaluations/cost-policy.js +20 -0
- package/src/evaluations/harness-attempt.js +113 -0
- package/src/evaluations/live-runner.js +55 -0
- package/src/evaluations/profile.js +28 -0
- package/src/evaluations/report.js +11 -0
- package/src/evaluations/text-attempt.js +18 -0
- package/src/evidence/checksum.js +87 -0
- package/src/evidence/collect.js +727 -0
- package/src/evidence/validate.js +354 -0
- package/src/feature/accepted-integration.js +46 -0
- package/src/feature/actions.js +37 -0
- package/src/feature/client-profile.js +56 -0
- package/src/feature/decomposition-contract.js +58 -0
- package/src/feature/host-execution.js +633 -0
- package/src/feature/host-lock-recovery.js +96 -0
- package/src/feature/host-run-lock.js +32 -0
- package/src/feature/local-approval.js +183 -0
- package/src/feature/plan-contract.js +215 -0
- package/src/feature/planner.js +253 -0
- package/src/feature/run-store.js +241 -0
- package/src/feature/runtime-bridge.js +840 -0
- package/src/feature/verification-report.js +168 -0
- package/src/feature/workflow.js +375 -0
- package/src/git/client.js +509 -0
- package/src/git/integration-worktree.js +153 -0
- package/src/git/reconcile.js +222 -0
- package/src/git/reservations.js +450 -0
- package/src/git/worktrees.js +478 -0
- package/src/graph/completion.js +189 -0
- package/src/graph/fixtures.js +116 -0
- package/src/graph/reducer.js +124 -0
- package/src/graph/scheduler.js +252 -0
- package/src/graph/validate.js +106 -0
- package/src/install/managed.js +579 -0
- package/src/install/project-reference.js +33 -0
- package/src/install/project-runtime.js +142 -0
- package/src/install/runtime-integrity.cjs +141 -0
- package/src/integrations/capabilities.js +69 -0
- package/src/integrations/host-observation.js +26 -0
- package/src/integrations/registry.js +56 -0
- package/src/models/delegate.js +102 -0
- package/src/models/profiles.js +53 -0
- package/src/models/protocols.js +208 -0
- package/src/models/registry.js +83 -0
- package/src/models/transport.js +59 -0
- package/src/policy/approvals.js +251 -0
- package/src/policy/authority.js +282 -0
- package/src/policy/budget.js +149 -0
- package/src/policy/command-bootstrap.js +219 -0
- package/src/policy/commands.js +803 -0
- package/src/prompts/launch-contract.js +43 -0
- package/src/prompts/planning-contract.js +92 -0
- package/src/protocols/presentation.js +53 -0
- package/src/protocols/project.js +289 -0
- package/src/quality/runner.js +497 -0
- package/src/quality/traceability.js +215 -0
- package/src/repositories/identity.js +59 -0
- package/src/repositories/index.js +2 -0
- package/src/repositories/provider.js +210 -0
- package/src/runtime/application.js +353 -0
- package/src/runtime/dependency-directory.js +33 -0
- package/src/runtime/harness-discovery.js +121 -0
- package/src/runtime/heartbeat.js +47 -0
- package/src/runtime/instance-store.js +112 -0
- package/src/runtime/orchestrator.js +1133 -0
- package/src/runtime/portable-dependencies.js +94 -0
- package/src/runtime/recovery.js +190 -0
- package/src/runtime/retry.js +54 -0
- package/src/runtime/supervisor.js +166 -0
- package/src/runtime/worktree-bootstrap.js +169 -0
- package/src/state/event-store.js +333 -0
- package/src/state/lock.js +266 -0
- package/src/state/paths.js +274 -0
- package/src/state/redact.js +106 -0
- package/src/state/snapshot-store.js +310 -0
- package/src/status/public/app.js +96 -0
- package/src/status/public/index.html +37 -0
- package/src/status/public/styles.css +50 -0
- package/src/status/server.js +323 -0
- package/src/status/view-model.js +356 -0
- package/src/support/bundle.js +195 -0
- package/src/support/failure-report.js +96 -0
- package/src/work-request/contract.js +250 -0
- package/src/work-request/host.js +78 -0
- package/src/work-request/local.js +163 -0
- package/src/work-request/tracker.js +105 -0
- package/templates/evidence/qa-bundle.json +194 -0
- package/templates/github-actions/rivet-deploy.yml +98 -0
- package/templates/harness/SKILL.md +90 -0
- package/templates/project/.rivet/orchestration.yaml +42 -0
- package/templates/project/.rivet/project.yaml +14 -0
- package/templates/project/.rivet/providers.yaml +8 -0
- package/templates/project/.rivet/quality.yaml +16 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { lstat } from 'node:fs/promises';
|
|
2
|
+
|
|
3
|
+
import { checkCompatibility, validVersion } from './compatibility.js';
|
|
4
|
+
import { createLaunchContract, failAgent } from './contract.js';
|
|
5
|
+
import { createProcessRunner } from './process-runner.js';
|
|
6
|
+
import { agentResultContract } from './result-contract.js';
|
|
7
|
+
import { featureDecompositionResultContract } from '../feature/decomposition-contract.js';
|
|
8
|
+
import { serializeLaunchContract } from '../prompts/launch-contract.js';
|
|
9
|
+
import { serializeProtocolLaunch } from '../protocols/presentation.js';
|
|
10
|
+
import { serializePlanningContract } from '../prompts/planning-contract.js';
|
|
11
|
+
import { CLAUDE_FEATURE_PROFILE } from '../feature/client-profile.js';
|
|
12
|
+
|
|
13
|
+
const ARGS = Object.freeze([
|
|
14
|
+
'--print', '--input-format', 'text', '--output-format', 'json', '--no-session-persistence', '{stdin}',
|
|
15
|
+
]);
|
|
16
|
+
const PLANNING_TOOLS = 'Read,Glob,Grep';
|
|
17
|
+
const PLANNING_SCHEMA = JSON.stringify(featureDecompositionResultContract().schema);
|
|
18
|
+
export const CLAUDE_ADAPTER_SYNTAX = Object.freeze({
|
|
19
|
+
version: 1,
|
|
20
|
+
provider: 'claude',
|
|
21
|
+
observedVersion: '2.1.207 (Claude Code)',
|
|
22
|
+
testedVersions: Object.freeze(['2.1.207 (Claude Code)', '2.1.274 (Claude Code)']),
|
|
23
|
+
requiredOptions: Object.freeze(['--print', '--input-format', '--output-format', '--no-session-persistence', '--model', '--effort', '--permission-mode', '--tools', '--json-schema', '--max-budget-usd']),
|
|
24
|
+
versionArgs: Object.freeze(['--version']),
|
|
25
|
+
args: ARGS,
|
|
26
|
+
inputMode: 'stdin-text',
|
|
27
|
+
outputMode: 'json-structured-output-envelope-v1',
|
|
28
|
+
optionalArgs: Object.freeze([
|
|
29
|
+
'--model <id>', '--effort <low|medium|high|max>', '--max-budget-usd <amount>',
|
|
30
|
+
'--permission-mode <acceptEdits|dontAsk|plan>', '--safe-mode',
|
|
31
|
+
'--tools <Read,Glob,Grep>', '--json-schema <approved-schema>',
|
|
32
|
+
]),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const CONFIG_KEYS = new Set(['executable', 'interpreter', 'expectedVersion', 'args', 'environment', 'timeoutMs', 'maxOutputBytes']);
|
|
36
|
+
const PLANNING_CONFIG_KEYS = new Set(['executable', 'interpreter', 'expectedVersion', 'worktree', 'environment', 'timeoutMs', 'maxOutputBytes']);
|
|
37
|
+
|
|
38
|
+
function stable(operation) {
|
|
39
|
+
try { return operation(); } catch { failAgent('template-invalid'); }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function capture(input, keys, required) {
|
|
43
|
+
return stable(() => {
|
|
44
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) failAgent('template-invalid');
|
|
45
|
+
const ownKeys = Reflect.ownKeys(input);
|
|
46
|
+
if (ownKeys.length > keys.size || ownKeys.some(key => typeof key !== 'string' || !keys.has(key))) failAgent('template-invalid');
|
|
47
|
+
const output = Object.create(null);
|
|
48
|
+
for (const key of ownKeys) {
|
|
49
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
50
|
+
if (!descriptor?.enumerable) failAgent('template-invalid');
|
|
51
|
+
output[key] = input[key];
|
|
52
|
+
}
|
|
53
|
+
if (required.some(key => !Object.hasOwn(output, key))) failAgent('template-invalid');
|
|
54
|
+
return output;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function captureArgs(input) {
|
|
59
|
+
return stable(() => {
|
|
60
|
+
if (!Array.isArray(input)) failAgent('template-invalid');
|
|
61
|
+
const prototype = Object.getPrototypeOf(input);
|
|
62
|
+
const length = input.length;
|
|
63
|
+
if (prototype !== Array.prototype || !Number.isSafeInteger(length)
|
|
64
|
+
|| length < ARGS.length || length > ARGS.length + 13) failAgent('template-invalid');
|
|
65
|
+
const result = [];
|
|
66
|
+
for (let index = 0; index < length; index += 1) {
|
|
67
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, String(index));
|
|
68
|
+
if (!descriptor?.enumerable) failAgent('template-invalid');
|
|
69
|
+
const value = input[index];
|
|
70
|
+
const maxLength = result.at(-1) === '--json-schema' ? 4096 : 200;
|
|
71
|
+
if (typeof value !== 'string' || value.length === 0 || value.length > maxLength || /[\u0000\r\n]/.test(value)) failAgent('template-invalid');
|
|
72
|
+
result.push(value);
|
|
73
|
+
}
|
|
74
|
+
const required = ARGS.slice(0, -1);
|
|
75
|
+
if (result.at(-1) !== '{stdin}' || result.filter(value => value === '{stdin}').length !== 1
|
|
76
|
+
|| required.some((value, index) => result[index] !== value)) failAgent('template-invalid');
|
|
77
|
+
const seen = new Set();
|
|
78
|
+
for (let index = required.length; index < result.length - 1; index += 1) {
|
|
79
|
+
const option = result[index];
|
|
80
|
+
if (seen.has(option)) failAgent('template-invalid');
|
|
81
|
+
seen.add(option);
|
|
82
|
+
if (option === '--safe-mode') continue;
|
|
83
|
+
const value = result[index + 1];
|
|
84
|
+
index += 1;
|
|
85
|
+
if (option === '--model' && /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/.test(value)) continue;
|
|
86
|
+
if (option === '--effort' && ['low', 'medium', 'high', 'max'].includes(value)) continue;
|
|
87
|
+
if (option === '--max-budget-usd' && /^(?:0|[1-9]\d*)(?:\.\d{1,6})?$/.test(value)
|
|
88
|
+
&& Number(value) > 0 && Number(value) <= 10_000) continue;
|
|
89
|
+
if (option === '--permission-mode' && ['acceptEdits', 'dontAsk', 'plan'].includes(value)) continue;
|
|
90
|
+
if (option === '--tools' && value === PLANNING_TOOLS) continue;
|
|
91
|
+
if (option === '--json-schema' && value === PLANNING_SCHEMA) continue;
|
|
92
|
+
failAgent('template-invalid');
|
|
93
|
+
}
|
|
94
|
+
return Object.freeze(result.filter(value => value !== '{stdin}'));
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function captureEnvironment(input) {
|
|
99
|
+
if (input === undefined) return undefined;
|
|
100
|
+
return stable(() => {
|
|
101
|
+
if (!input || typeof input !== 'object' || Array.isArray(input) || Object.getPrototypeOf(input) !== Object.prototype) failAgent('template-invalid');
|
|
102
|
+
const keys = Reflect.ownKeys(input);
|
|
103
|
+
if (keys.length > 64 || keys.some(key => typeof key !== 'string')) failAgent('template-invalid');
|
|
104
|
+
const output = Object.create(null);
|
|
105
|
+
for (const key of keys) {
|
|
106
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
107
|
+
if (!descriptor?.enumerable) failAgent('template-invalid');
|
|
108
|
+
const value = input[key];
|
|
109
|
+
if (typeof value !== 'string') failAgent('template-invalid');
|
|
110
|
+
output[key] = value;
|
|
111
|
+
}
|
|
112
|
+
return Object.freeze(output);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function createClaudeClient(input) {
|
|
117
|
+
const config = capture(input, CONFIG_KEYS, ['executable', 'args']);
|
|
118
|
+
if (typeof config.executable !== 'string' || !config.executable.startsWith('/') || config.executable.length > 1024
|
|
119
|
+
|| (config.interpreter !== undefined && (typeof config.interpreter !== 'string' || !config.interpreter.startsWith('/') || config.interpreter.length > 1024))
|
|
120
|
+
|| (config.expectedVersion !== undefined && !validVersion(config.expectedVersion))) failAgent('template-invalid');
|
|
121
|
+
const args = captureArgs(config.args);
|
|
122
|
+
if (['--max-budget-usd', '--tools', '--json-schema'].some(option => args.includes(option))) failAgent('template-invalid');
|
|
123
|
+
const environment = captureEnvironment(config.environment);
|
|
124
|
+
const executable = config.executable;
|
|
125
|
+
const interpreter = config.interpreter;
|
|
126
|
+
const expectedVersion = config.expectedVersion;
|
|
127
|
+
const timeoutMs = config.timeoutMs;
|
|
128
|
+
const maxOutputBytes = config.maxOutputBytes;
|
|
129
|
+
|
|
130
|
+
async function launch(contractInput, optionInput = {}) {
|
|
131
|
+
const options = capture(optionInput, new Set(['signal','protocolContext']), []);
|
|
132
|
+
const signalState = stable(() => {
|
|
133
|
+
const value = options.signal;
|
|
134
|
+
if (value !== undefined && (!(value instanceof AbortSignal) || Object.getPrototypeOf(value) !== AbortSignal.prototype)) failAgent('template-invalid');
|
|
135
|
+
return Object.freeze({ value, aborted: value?.aborted === true });
|
|
136
|
+
});
|
|
137
|
+
if (signalState.aborted) failAgent('aborted');
|
|
138
|
+
const contract = createLaunchContract(contractInput);
|
|
139
|
+
serializeProtocolLaunch(contract, options.protocolContext);
|
|
140
|
+
const runner = await createProcessRunner({
|
|
141
|
+
executable, interpreter, worktree: contract.worktree.path,
|
|
142
|
+
worktreeIdentity: { dev: contract.worktree.dev, ino: contract.worktree.ino },
|
|
143
|
+
environment, signal: signalState.value,
|
|
144
|
+
timeoutMs: Math.min(timeoutMs ?? contract.budget.maxRuntimeMs, contract.budget.maxRuntimeMs, 10 * 60_000),
|
|
145
|
+
maxOutputBytes, allowOptionArgs: true, resultFormat: 'claude-json',
|
|
146
|
+
});
|
|
147
|
+
await checkCompatibility(runner, 'claude', [...args, '--json-schema', '--max-budget-usd'], expectedVersion);
|
|
148
|
+
return runner.run({
|
|
149
|
+
args: Object.freeze([
|
|
150
|
+
...args,
|
|
151
|
+
'--json-schema', JSON.stringify(agentResultContract(contract.evidence).schema),
|
|
152
|
+
'--max-budget-usd', String(contract.budget.maxCostUsd),
|
|
153
|
+
]),
|
|
154
|
+
cwd: '.', payload: serializeLaunchContract(contract), protocolContext: options.protocolContext, signal: signalState.value,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return Object.freeze({ provider: 'claude', syntaxVersion: 1, launch });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function createClaudePlanningClient(input) {
|
|
162
|
+
const config = capture(input, PLANNING_CONFIG_KEYS, ['executable', 'worktree']);
|
|
163
|
+
if (typeof config.executable !== 'string' || !config.executable.startsWith('/') || config.executable.length > 1024
|
|
164
|
+
|| (config.interpreter !== undefined && (typeof config.interpreter !== 'string' || !config.interpreter.startsWith('/') || config.interpreter.length > 1024))
|
|
165
|
+
|| (config.expectedVersion !== undefined && !validVersion(config.expectedVersion))
|
|
166
|
+
|| typeof config.worktree !== 'string' || !config.worktree.startsWith('/') || config.worktree.length > 1024) {
|
|
167
|
+
failAgent('template-invalid');
|
|
168
|
+
}
|
|
169
|
+
const args = captureArgs([
|
|
170
|
+
...ARGS.slice(0, -1),
|
|
171
|
+
'--model', CLAUDE_FEATURE_PROFILE.model,
|
|
172
|
+
'--effort', CLAUDE_FEATURE_PROFILE.planning.effort,
|
|
173
|
+
'--max-budget-usd', String(CLAUDE_FEATURE_PROFILE.planning.maxCostUsd),
|
|
174
|
+
'--permission-mode', 'dontAsk',
|
|
175
|
+
'--tools', PLANNING_TOOLS,
|
|
176
|
+
'--json-schema', PLANNING_SCHEMA,
|
|
177
|
+
'{stdin}',
|
|
178
|
+
]);
|
|
179
|
+
const environment = captureEnvironment(config.environment);
|
|
180
|
+
const executable = config.executable;
|
|
181
|
+
const interpreter = config.interpreter;
|
|
182
|
+
const expectedVersion = config.expectedVersion;
|
|
183
|
+
const worktree = config.worktree;
|
|
184
|
+
|
|
185
|
+
async function propose(contract, optionInput = {}) {
|
|
186
|
+
const options = capture(optionInput, new Set(['signal']), []);
|
|
187
|
+
const signal = options.signal;
|
|
188
|
+
if (signal !== undefined && (!(signal instanceof AbortSignal) || Object.getPrototypeOf(signal) !== AbortSignal.prototype)) {
|
|
189
|
+
failAgent('template-invalid');
|
|
190
|
+
}
|
|
191
|
+
if (signal?.aborted) failAgent('aborted');
|
|
192
|
+
let metadata;
|
|
193
|
+
try { metadata = await lstat(worktree, { bigint: true }); } catch { failAgent('provider-unavailable'); }
|
|
194
|
+
if (!metadata.isDirectory() || metadata.isSymbolicLink()) failAgent('provider-unavailable');
|
|
195
|
+
const identity = Object.freeze({ dev: metadata.dev.toString(), ino: metadata.ino.toString() });
|
|
196
|
+
const runner = await createProcessRunner({
|
|
197
|
+
executable, interpreter, worktree, worktreeIdentity: identity, environment, signal,
|
|
198
|
+
timeoutMs: config.timeoutMs, maxInputBytes: 512 * 1024,
|
|
199
|
+
maxOutputBytes: config.maxOutputBytes ?? 512 * 1024, allowOptionArgs: true,
|
|
200
|
+
});
|
|
201
|
+
await checkCompatibility(runner, 'claude', [...args, '--json-schema', '--max-budget-usd'], expectedVersion);
|
|
202
|
+
return runner.runPlanning({
|
|
203
|
+
args,
|
|
204
|
+
cwd: '.',
|
|
205
|
+
payload: serializePlanningContract({
|
|
206
|
+
worktree: { path: worktree, ...identity, reservationId: 'planning-read-only' },
|
|
207
|
+
contract,
|
|
208
|
+
}),
|
|
209
|
+
signal,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return Object.freeze({ provider: 'claude', syntaxVersion: 1, propose });
|
|
214
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { lstat } from 'node:fs/promises';
|
|
2
|
+
|
|
3
|
+
import { checkCompatibility, validVersion } from './compatibility.js';
|
|
4
|
+
import { createLaunchContract, failAgent } from './contract.js';
|
|
5
|
+
import { createProcessRunner } from './process-runner.js';
|
|
6
|
+
import { serializeLaunchContract } from '../prompts/launch-contract.js';
|
|
7
|
+
import { serializeProtocolLaunch } from '../protocols/presentation.js';
|
|
8
|
+
import { serializePlanningContract } from '../prompts/planning-contract.js';
|
|
9
|
+
|
|
10
|
+
const ARGS = Object.freeze(['exec', '--ephemeral', '--ignore-user-config', '--color', 'never', '{stdin}']);
|
|
11
|
+
export const CODEX_ADAPTER_SYNTAX = Object.freeze({
|
|
12
|
+
version: 1,
|
|
13
|
+
provider: 'codex',
|
|
14
|
+
observedVersion: 'codex-cli 0.148.0-alpha.9',
|
|
15
|
+
testedVersions: Object.freeze(['codex-cli 0.148.0-alpha.9', 'codex-cli 0.155.0-alpha.16']),
|
|
16
|
+
requiredOptions: Object.freeze(['--ephemeral', '--ignore-user-config', '--color', '--sandbox']),
|
|
17
|
+
versionArgs: Object.freeze(['--version']),
|
|
18
|
+
args: ARGS,
|
|
19
|
+
inputMode: 'stdin-text',
|
|
20
|
+
outputMode: 'direct-final-text-envelope-v1',
|
|
21
|
+
optionalArgs: Object.freeze(['--model <id>', '--sandbox <read-only|workspace-write>', '--strict-config']),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const CONFIG_KEYS = new Set(['executable', 'interpreter', 'expectedVersion', 'args', 'environment', 'timeoutMs', 'maxOutputBytes']);
|
|
25
|
+
const PLANNING_CONFIG_KEYS = new Set(['executable', 'interpreter', 'expectedVersion', 'worktree', 'environment', 'timeoutMs', 'maxOutputBytes']);
|
|
26
|
+
|
|
27
|
+
function stable(operation) {
|
|
28
|
+
try { return operation(); } catch { failAgent('template-invalid'); }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function capture(input, keys, required) {
|
|
32
|
+
return stable(() => {
|
|
33
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) failAgent('template-invalid');
|
|
34
|
+
const ownKeys = Reflect.ownKeys(input);
|
|
35
|
+
if (ownKeys.length > keys.size || ownKeys.some(key => typeof key !== 'string' || !keys.has(key))) failAgent('template-invalid');
|
|
36
|
+
const output = Object.create(null);
|
|
37
|
+
for (const key of ownKeys) {
|
|
38
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
39
|
+
if (!descriptor?.enumerable) failAgent('template-invalid');
|
|
40
|
+
output[key] = input[key];
|
|
41
|
+
}
|
|
42
|
+
if (required.some(key => !Object.hasOwn(output, key))) failAgent('template-invalid');
|
|
43
|
+
return output;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function captureArgs(input) {
|
|
48
|
+
return stable(() => {
|
|
49
|
+
if (!Array.isArray(input)) failAgent('template-invalid');
|
|
50
|
+
const prototype = Object.getPrototypeOf(input);
|
|
51
|
+
const length = input.length;
|
|
52
|
+
if (prototype !== Array.prototype || !Number.isSafeInteger(length)
|
|
53
|
+
|| length < ARGS.length || length > ARGS.length + 6) failAgent('template-invalid');
|
|
54
|
+
const result = [];
|
|
55
|
+
for (let index = 0; index < length; index += 1) {
|
|
56
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, String(index));
|
|
57
|
+
if (!descriptor?.enumerable) failAgent('template-invalid');
|
|
58
|
+
const value = input[index];
|
|
59
|
+
if (typeof value !== 'string' || value.length === 0 || value.length > 200 || /[\u0000\r\n]/.test(value)) failAgent('template-invalid');
|
|
60
|
+
result.push(value);
|
|
61
|
+
}
|
|
62
|
+
const required = ARGS.slice(0, -1);
|
|
63
|
+
if (result.at(-1) !== '{stdin}' || result.filter(value => value === '{stdin}').length !== 1
|
|
64
|
+
|| required.some((value, index) => result[index] !== value)) failAgent('template-invalid');
|
|
65
|
+
const seen = new Set();
|
|
66
|
+
for (let index = required.length; index < result.length - 1; index += 1) {
|
|
67
|
+
const option = result[index];
|
|
68
|
+
if (seen.has(option)) failAgent('template-invalid');
|
|
69
|
+
seen.add(option);
|
|
70
|
+
if (option === '--strict-config') continue;
|
|
71
|
+
const value = result[index + 1];
|
|
72
|
+
index += 1;
|
|
73
|
+
if (option === '--model' && /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/.test(value)) continue;
|
|
74
|
+
if (option === '--sandbox' && ['read-only', 'workspace-write'].includes(value)) continue;
|
|
75
|
+
failAgent('template-invalid');
|
|
76
|
+
}
|
|
77
|
+
return Object.freeze(result.filter(value => value !== '{stdin}'));
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function captureEnvironment(input) {
|
|
82
|
+
if (input === undefined) return undefined;
|
|
83
|
+
return stable(() => {
|
|
84
|
+
if (!input || typeof input !== 'object' || Array.isArray(input) || Object.getPrototypeOf(input) !== Object.prototype) failAgent('template-invalid');
|
|
85
|
+
const keys = Reflect.ownKeys(input);
|
|
86
|
+
if (keys.length > 64 || keys.some(key => typeof key !== 'string')) failAgent('template-invalid');
|
|
87
|
+
const output = Object.create(null);
|
|
88
|
+
for (const key of keys) {
|
|
89
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
90
|
+
if (!descriptor?.enumerable) failAgent('template-invalid');
|
|
91
|
+
const value = input[key];
|
|
92
|
+
if (typeof value !== 'string') failAgent('template-invalid');
|
|
93
|
+
output[key] = value;
|
|
94
|
+
}
|
|
95
|
+
return Object.freeze(output);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function createCodexClient(input) {
|
|
100
|
+
const config = capture(input, CONFIG_KEYS, ['executable', 'args']);
|
|
101
|
+
if (typeof config.executable !== 'string' || !config.executable.startsWith('/') || config.executable.length > 1024
|
|
102
|
+
|| (config.interpreter !== undefined && (typeof config.interpreter !== 'string' || !config.interpreter.startsWith('/') || config.interpreter.length > 1024))
|
|
103
|
+
|| (config.expectedVersion !== undefined && !validVersion(config.expectedVersion))) failAgent('template-invalid');
|
|
104
|
+
const args = captureArgs(config.args);
|
|
105
|
+
const environment = captureEnvironment(config.environment);
|
|
106
|
+
const executable = config.executable;
|
|
107
|
+
const interpreter = config.interpreter;
|
|
108
|
+
const expectedVersion = config.expectedVersion;
|
|
109
|
+
const timeoutMs = config.timeoutMs;
|
|
110
|
+
const maxOutputBytes = config.maxOutputBytes;
|
|
111
|
+
|
|
112
|
+
async function launch(contractInput, optionInput = {}) {
|
|
113
|
+
const options = capture(optionInput, new Set(['signal','protocolContext']), []);
|
|
114
|
+
const signalState = stable(() => {
|
|
115
|
+
const value = options.signal;
|
|
116
|
+
if (value !== undefined && (!(value instanceof AbortSignal) || Object.getPrototypeOf(value) !== AbortSignal.prototype)) failAgent('template-invalid');
|
|
117
|
+
return Object.freeze({ value, aborted: value?.aborted === true });
|
|
118
|
+
});
|
|
119
|
+
if (signalState.aborted) failAgent('aborted');
|
|
120
|
+
const contract = createLaunchContract(contractInput);
|
|
121
|
+
serializeProtocolLaunch(contract, options.protocolContext);
|
|
122
|
+
const runner = await createProcessRunner({
|
|
123
|
+
executable, interpreter, worktree: contract.worktree.path,
|
|
124
|
+
worktreeIdentity: { dev: contract.worktree.dev, ino: contract.worktree.ino },
|
|
125
|
+
environment, signal: signalState.value,
|
|
126
|
+
timeoutMs: Math.min(timeoutMs ?? contract.budget.maxRuntimeMs, contract.budget.maxRuntimeMs, 10 * 60_000),
|
|
127
|
+
maxOutputBytes, allowOptionArgs: true,
|
|
128
|
+
});
|
|
129
|
+
await checkCompatibility(runner, 'codex', args, expectedVersion);
|
|
130
|
+
return runner.run({ args, cwd: '.', payload: serializeLaunchContract(contract), protocolContext: options.protocolContext, signal: signalState.value });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return Object.freeze({ provider: 'codex', syntaxVersion: 1, launch });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function createCodexPlanningClient(input) {
|
|
137
|
+
const config = capture(input, PLANNING_CONFIG_KEYS, ['executable', 'worktree']);
|
|
138
|
+
if (typeof config.executable !== 'string' || !config.executable.startsWith('/') || config.executable.length > 1024
|
|
139
|
+
|| (config.interpreter !== undefined && (typeof config.interpreter !== 'string' || !config.interpreter.startsWith('/') || config.interpreter.length > 1024))
|
|
140
|
+
|| (config.expectedVersion !== undefined && !validVersion(config.expectedVersion))
|
|
141
|
+
|| typeof config.worktree !== 'string' || !config.worktree.startsWith('/') || config.worktree.length > 1024) {
|
|
142
|
+
failAgent('template-invalid');
|
|
143
|
+
}
|
|
144
|
+
const args = captureArgs([...ARGS.slice(0, -1), '--sandbox', 'read-only', '{stdin}']);
|
|
145
|
+
const environment = captureEnvironment(config.environment);
|
|
146
|
+
const executable = config.executable;
|
|
147
|
+
const interpreter = config.interpreter;
|
|
148
|
+
const expectedVersion = config.expectedVersion;
|
|
149
|
+
const worktree = config.worktree;
|
|
150
|
+
|
|
151
|
+
async function propose(contract, optionInput = {}) {
|
|
152
|
+
const options = capture(optionInput, new Set(['signal']), []);
|
|
153
|
+
const signal = options.signal;
|
|
154
|
+
if (signal !== undefined && (!(signal instanceof AbortSignal) || Object.getPrototypeOf(signal) !== AbortSignal.prototype)) {
|
|
155
|
+
failAgent('template-invalid');
|
|
156
|
+
}
|
|
157
|
+
if (signal?.aborted) failAgent('aborted');
|
|
158
|
+
let metadata;
|
|
159
|
+
try { metadata = await lstat(worktree, { bigint: true }); } catch { failAgent('provider-unavailable'); }
|
|
160
|
+
if (!metadata.isDirectory() || metadata.isSymbolicLink()) failAgent('provider-unavailable');
|
|
161
|
+
const identity = Object.freeze({ dev: metadata.dev.toString(), ino: metadata.ino.toString() });
|
|
162
|
+
const runner = await createProcessRunner({
|
|
163
|
+
executable, interpreter, worktree, worktreeIdentity: identity, environment, signal,
|
|
164
|
+
timeoutMs: config.timeoutMs, maxInputBytes: 512 * 1024,
|
|
165
|
+
maxOutputBytes: config.maxOutputBytes ?? 512 * 1024, allowOptionArgs: true,
|
|
166
|
+
});
|
|
167
|
+
await checkCompatibility(runner, 'codex', args, expectedVersion);
|
|
168
|
+
return runner.runPlanning({
|
|
169
|
+
args,
|
|
170
|
+
cwd: '.',
|
|
171
|
+
payload: serializePlanningContract({
|
|
172
|
+
worktree: { path: worktree, ...identity, reservationId: 'planning-read-only' },
|
|
173
|
+
contract,
|
|
174
|
+
}),
|
|
175
|
+
signal,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return Object.freeze({ provider: 'codex', syntaxVersion: 1, propose });
|
|
180
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { failAgent } from './contract.js';
|
|
2
|
+
|
|
3
|
+
// Evidence of tested releases belongs in docs, not an executable allowlist.
|
|
4
|
+
export function validVersion(value) {
|
|
5
|
+
return typeof value === 'string' && value.trim().length > 0 && value.length <= 200
|
|
6
|
+
&& !/[\u0000-\u001f\u007f]/.test(value);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function missingCapabilities(help, required) {
|
|
10
|
+
const declared = new Set();
|
|
11
|
+
for (const line of help.split('\n')) {
|
|
12
|
+
const match = line.match(/^\s*(?:-[A-Za-z?],?\s+)?(--[a-z][a-z0-9-]*)(?=[\s=,]|$)/);
|
|
13
|
+
if (match) declared.add(match[1]);
|
|
14
|
+
}
|
|
15
|
+
return required.filter(option => !declared.has(option));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function checkCompatibility(runner, provider, args, expectedVersion) {
|
|
19
|
+
const observed = await runner.probeVersion();
|
|
20
|
+
if (!validVersion(observed) || (expectedVersion !== undefined && observed !== expectedVersion)) failAgent('provider-unavailable');
|
|
21
|
+
const required = [...new Set(args.filter(value => /^--[a-z][a-z0-9-]*$/.test(value)))];
|
|
22
|
+
if (missingCapabilities(await runner.probeHelp(provider), required).length) failAgent('provider-unavailable');
|
|
23
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
const CONTRACT_KEYS = new Set([
|
|
2
|
+
'nodeId', 'parentId', 'objective', 'ownedPaths', 'authority', 'commands', 'evidence',
|
|
3
|
+
'budget', 'worktree', 'contextRefs', 'heartbeatInterval', 'stopConditions',
|
|
4
|
+
]);
|
|
5
|
+
const ID = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
6
|
+
const REF = /^[a-zA-Z0-9][a-zA-Z0-9._:-]*$/;
|
|
7
|
+
const ACTION = /^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/;
|
|
8
|
+
const WINDOWS_RESERVED = /^(?:con|prn|aux|nul|conin\$|conout\$|com[1-9]|lpt[1-9])(?:\..*)?$/i;
|
|
9
|
+
const TOKEN_VALUE = /(?:\b(?:Bearer|Basic)\s+[A-Za-z0-9+/_.:=-]{8,}|\b(?:sk-|gh[pousr]_|github_pat_|xox[baprs]-)[A-Za-z0-9_-]{8,}|\bAKIA[0-9A-Z]{16}\b)/i;
|
|
10
|
+
const SENSITIVE_LABELS = Object.freeze([
|
|
11
|
+
'password', 'passwd', 'secret', 'apikey', 'accesskey', 'privatekey', 'authorization',
|
|
12
|
+
'credential', 'credentials', 'token', 'accesstoken', 'apitoken', 'authtoken',
|
|
13
|
+
]);
|
|
14
|
+
const launchContracts = new WeakSet();
|
|
15
|
+
|
|
16
|
+
export class AgentContractError extends Error {
|
|
17
|
+
constructor(reason = 'invalid-contract') {
|
|
18
|
+
const messages = {
|
|
19
|
+
'invalid-contract': 'Agent launch contract is invalid.',
|
|
20
|
+
'invalid-script': 'Fake agent script is invalid.',
|
|
21
|
+
'output-invalid': 'Agent output is invalid.',
|
|
22
|
+
'output-overflow': 'Agent output exceeded its safe limit.',
|
|
23
|
+
timeout: 'Agent execution timed out.',
|
|
24
|
+
'launch-timeout': 'Agent provider launch timed out.',
|
|
25
|
+
'unsupported-platform': 'Agent clients are unsupported on this platform.',
|
|
26
|
+
aborted: 'Agent execution was cancelled.',
|
|
27
|
+
'provider-unavailable': 'Configured agent provider is unavailable.',
|
|
28
|
+
'executable-unsafe': 'Configured agent executable is unsafe.',
|
|
29
|
+
'cwd-unsafe': 'Agent working directory is unsafe.',
|
|
30
|
+
'argument-invalid': 'Agent provider argument is invalid.',
|
|
31
|
+
'template-invalid': 'Agent provider template is invalid.',
|
|
32
|
+
'spawn-failed': 'Agent provider could not be started safely.',
|
|
33
|
+
};
|
|
34
|
+
super(messages[reason] ?? messages['invalid-contract']);
|
|
35
|
+
this.name = 'AgentContractError';
|
|
36
|
+
this.code = `ERR_AGENT_${reason.replaceAll('-', '_').toUpperCase()}`;
|
|
37
|
+
this.safeMessage = this.message;
|
|
38
|
+
this.details = Object.freeze({ reason });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function failAgent(reason) { throw new AgentContractError(reason); }
|
|
43
|
+
|
|
44
|
+
function captureRecord(input, keys, required, reason = 'invalid-contract') {
|
|
45
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) failAgent(reason);
|
|
46
|
+
let ownKeys;
|
|
47
|
+
try { ownKeys = Reflect.ownKeys(input); } catch { failAgent(reason); }
|
|
48
|
+
if (ownKeys.length > keys.size || ownKeys.some(key => typeof key !== 'string' || !keys.has(key))) failAgent(reason);
|
|
49
|
+
const output = Object.create(null);
|
|
50
|
+
try {
|
|
51
|
+
for (const key of ownKeys) {
|
|
52
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
53
|
+
if (!descriptor?.enumerable) failAgent(reason);
|
|
54
|
+
output[key] = input[key];
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {
|
|
57
|
+
failAgent(reason);
|
|
58
|
+
}
|
|
59
|
+
if (required.some(key => !Object.hasOwn(output, key))) failAgent(reason);
|
|
60
|
+
return output;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function containsSecretMaterial(value) {
|
|
64
|
+
if (typeof value !== 'string') return false;
|
|
65
|
+
if (value.length > 1024 * 1024) return true;
|
|
66
|
+
let detection;
|
|
67
|
+
try { detection = value.normalize('NFKC').replace(/\p{Cf}/gu, ''); } catch { return true; }
|
|
68
|
+
if (TOKEN_VALUE.test(detection)) return true;
|
|
69
|
+
const separator = character => character === ' ' || character === '\t' || character === '.' || character === '-' || character === '_';
|
|
70
|
+
const wrapper = character => character === '"' || character === "'" || character === '“' || character === '”'
|
|
71
|
+
|| character === '‘' || character === '’' || character === '\\';
|
|
72
|
+
for (let delimiter = 0; delimiter < detection.length; delimiter += 1) {
|
|
73
|
+
if (detection[delimiter] !== ':' && detection[delimiter] !== '=') continue;
|
|
74
|
+
let valueIndex = delimiter + 1;
|
|
75
|
+
while (valueIndex < detection.length && (separator(detection[valueIndex]) || wrapper(detection[valueIndex]))) valueIndex += 1;
|
|
76
|
+
if (valueIndex >= detection.length || ',}]'.includes(detection[valueIndex])) continue;
|
|
77
|
+
for (const label of SENSITIVE_LABELS) {
|
|
78
|
+
let cursor = delimiter - 1;
|
|
79
|
+
while (cursor >= 0 && (separator(detection[cursor]) || wrapper(detection[cursor]))) cursor -= 1;
|
|
80
|
+
let labelIndex = label.length - 1;
|
|
81
|
+
while (labelIndex >= 0 && cursor >= 0) {
|
|
82
|
+
while (cursor >= 0 && (separator(detection[cursor]) || wrapper(detection[cursor]))) cursor -= 1;
|
|
83
|
+
if (cursor < 0 || detection[cursor].toLowerCase() !== label[labelIndex]) break;
|
|
84
|
+
cursor -= 1;
|
|
85
|
+
labelIndex -= 1;
|
|
86
|
+
}
|
|
87
|
+
if (labelIndex >= 0) continue;
|
|
88
|
+
while (cursor >= 0 && wrapper(detection[cursor])) cursor -= 1;
|
|
89
|
+
if (cursor < 0 || !/[A-Za-z0-9]/.test(detection[cursor])) return true;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function string(value, maximum, pattern) {
|
|
96
|
+
if (typeof value !== 'string' || value.length === 0 || value.length > maximum
|
|
97
|
+
|| value.normalize('NFKC') !== value || /[\u0000]/.test(value) || containsSecretMaterial(value)
|
|
98
|
+
|| (pattern && !pattern.test(value))) failAgent('invalid-contract');
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function id(value) { return string(value, 64, ID); }
|
|
103
|
+
|
|
104
|
+
function relativePath(value) {
|
|
105
|
+
string(value, 500);
|
|
106
|
+
if (value.startsWith('/') || value.startsWith('-') || value.endsWith('/') || value.includes('//')
|
|
107
|
+
|| /[\\:\u0000-\u001f\u007f]/.test(value)) failAgent('invalid-contract');
|
|
108
|
+
const parts = value.split('/');
|
|
109
|
+
if (parts.some(part => !part || part === '.' || part === '..' || part.endsWith('.') || part.endsWith(' ')
|
|
110
|
+
|| part.toUpperCase().toLowerCase() === '.git' || WINDOWS_RESERVED.test(part))) failAgent('invalid-contract');
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function array(input, maximum, convert) {
|
|
115
|
+
if (!Array.isArray(input)) failAgent('invalid-contract');
|
|
116
|
+
let length;
|
|
117
|
+
try { length = input.length; } catch { failAgent('invalid-contract'); }
|
|
118
|
+
if (!Number.isSafeInteger(length) || length > maximum) failAgent('invalid-contract');
|
|
119
|
+
const output = [];
|
|
120
|
+
try {
|
|
121
|
+
for (let index = 0; index < length; index += 1) {
|
|
122
|
+
if (!Object.hasOwn(input, index)) failAgent('invalid-contract');
|
|
123
|
+
output.push(convert(input[index]));
|
|
124
|
+
}
|
|
125
|
+
} catch (error) {
|
|
126
|
+
failAgent('invalid-contract');
|
|
127
|
+
}
|
|
128
|
+
if (new Set(output.map(value => typeof value === 'string' ? value.toUpperCase().toLowerCase() : value)).size !== output.length) failAgent('invalid-contract');
|
|
129
|
+
return Object.freeze(output);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function authority(input) {
|
|
133
|
+
const value = captureRecord(input, new Set(['actions', 'providers']), ['actions', 'providers']);
|
|
134
|
+
return Object.freeze({
|
|
135
|
+
actions: array(value.actions, 64, child => string(child, 100, ACTION)),
|
|
136
|
+
providers: array(value.providers, 32, child => string(child, 100, ACTION)),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function budget(input) {
|
|
141
|
+
const value = captureRecord(input, new Set(['maxTokens', 'maxRuntimeMs', 'maxCostUsd']), ['maxTokens', 'maxRuntimeMs', 'maxCostUsd']);
|
|
142
|
+
if (!Number.isSafeInteger(value.maxTokens) || value.maxTokens <= 0 || value.maxTokens > 10_000_000
|
|
143
|
+
|| !Number.isSafeInteger(value.maxRuntimeMs) || value.maxRuntimeMs <= 0 || value.maxRuntimeMs > 86_400_000
|
|
144
|
+
|| typeof value.maxCostUsd !== 'number' || !Number.isFinite(value.maxCostUsd) || value.maxCostUsd < 0 || value.maxCostUsd > 100_000) {
|
|
145
|
+
failAgent('invalid-contract');
|
|
146
|
+
}
|
|
147
|
+
return Object.freeze({ maxTokens: value.maxTokens, maxRuntimeMs: value.maxRuntimeMs, maxCostUsd: value.maxCostUsd });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function captureWorktree(input) {
|
|
151
|
+
const value = captureRecord(input, new Set(['path', 'dev', 'ino', 'reservationId']), ['path', 'dev', 'ino', 'reservationId']);
|
|
152
|
+
const path = string(value.path, 1024);
|
|
153
|
+
if (!isAbsolute(path) || resolve(path) !== path || /[\\:\r\n]/.test(path)) failAgent('invalid-contract');
|
|
154
|
+
return Object.freeze({
|
|
155
|
+
path, dev: string(value.dev, 64, /^[0-9]+$/), ino: string(value.ino, 64, /^[0-9]+$/), reservationId: id(value.reservationId),
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function createLaunchContract(input) {
|
|
160
|
+
const value = captureRecord(input, CONTRACT_KEYS, [...CONTRACT_KEYS]);
|
|
161
|
+
const parentId = value.parentId === null ? null : id(value.parentId);
|
|
162
|
+
if (!Number.isSafeInteger(value.heartbeatInterval) || value.heartbeatInterval < 100 || value.heartbeatInterval > 3_600_000) failAgent('invalid-contract');
|
|
163
|
+
const contract = Object.freeze({
|
|
164
|
+
version: 1,
|
|
165
|
+
nodeId: id(value.nodeId), parentId,
|
|
166
|
+
objective: string(value.objective, 16_384),
|
|
167
|
+
ownedPaths: array(value.ownedPaths, 256, relativePath),
|
|
168
|
+
authority: authority(value.authority),
|
|
169
|
+
commands: array(value.commands, 128, child => string(child, 100, ACTION)),
|
|
170
|
+
evidence: array(value.evidence, 128, child => string(child, 200, REF)),
|
|
171
|
+
budget: budget(value.budget), worktree: captureWorktree(value.worktree),
|
|
172
|
+
contextRefs: array(value.contextRefs, 128, child => string(child, 200, REF)),
|
|
173
|
+
heartbeatInterval: value.heartbeatInterval,
|
|
174
|
+
stopConditions: array(value.stopConditions, 64, child => string(child, 200, ACTION)),
|
|
175
|
+
});
|
|
176
|
+
launchContracts.add(contract);
|
|
177
|
+
return contract;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function isLaunchContract(value) { return launchContracts.has(value); }
|
|
181
|
+
|
|
182
|
+
export function immutableJson(value, reason = 'output-invalid') {
|
|
183
|
+
const active = new WeakSet();
|
|
184
|
+
function clone(child, depth) {
|
|
185
|
+
if (depth > 16) failAgent(reason);
|
|
186
|
+
if (child === null || typeof child === 'boolean' || typeof child === 'string') {
|
|
187
|
+
if (typeof child === 'string' && child.length > 16_384) failAgent(reason);
|
|
188
|
+
return child;
|
|
189
|
+
}
|
|
190
|
+
if (typeof child === 'number') {
|
|
191
|
+
if (!Number.isFinite(child)) failAgent(reason);
|
|
192
|
+
return child;
|
|
193
|
+
}
|
|
194
|
+
if (!child || typeof child !== 'object' || active.has(child)) failAgent(reason);
|
|
195
|
+
active.add(child);
|
|
196
|
+
try {
|
|
197
|
+
if (Array.isArray(child)) {
|
|
198
|
+
if (child.length > 256) failAgent(reason);
|
|
199
|
+
const output = [];
|
|
200
|
+
for (let index = 0; index < child.length; index += 1) {
|
|
201
|
+
const descriptor = Object.getOwnPropertyDescriptor(child, String(index));
|
|
202
|
+
if (!descriptor?.enumerable || !Object.hasOwn(descriptor, 'value')) failAgent(reason);
|
|
203
|
+
output.push(clone(descriptor.value, depth + 1));
|
|
204
|
+
}
|
|
205
|
+
return Object.freeze(output);
|
|
206
|
+
}
|
|
207
|
+
const prototype = Object.getPrototypeOf(child);
|
|
208
|
+
if (prototype !== Object.prototype && prototype !== null) failAgent(reason);
|
|
209
|
+
const keys = Reflect.ownKeys(child);
|
|
210
|
+
if (keys.length > 256 || keys.some(key => typeof key !== 'string')) failAgent(reason);
|
|
211
|
+
const output = Object.create(null);
|
|
212
|
+
for (const key of keys) {
|
|
213
|
+
const descriptor = Object.getOwnPropertyDescriptor(child, key);
|
|
214
|
+
if (!descriptor?.enumerable || !Object.hasOwn(descriptor, 'value') || key.length > 200) failAgent(reason);
|
|
215
|
+
output[key] = clone(descriptor.value, depth + 1);
|
|
216
|
+
}
|
|
217
|
+
return Object.freeze(output);
|
|
218
|
+
} finally { active.delete(child); }
|
|
219
|
+
}
|
|
220
|
+
try { return clone(value, 0); } catch (error) {
|
|
221
|
+
failAgent(reason);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
import { isAbsolute, resolve } from 'node:path';
|