@fitlab-ai/agent-infra 0.9.14 → 0.10.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/README.md +10 -0
- package/README.zh-CN.md +10 -0
- package/bin/internal-cli.sh +45 -0
- package/dist/bin/internal-cli.js +93 -30
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +9 -4
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
- package/dist/lib/defaults.json +103 -103
- package/dist/lib/fs/durable-write.js +39 -0
- package/dist/lib/fs/file-lock.js +111 -0
- package/dist/lib/host-control/audit.js +19 -0
- package/dist/lib/host-control/client.js +142 -0
- package/dist/lib/host-control/command.js +49 -0
- package/dist/lib/host-control/path.js +160 -0
- package/dist/lib/host-control/secure-fs.js +140 -0
- package/dist/lib/host-control/server.js +159 -0
- package/dist/lib/host-control/service.js +112 -0
- package/dist/lib/init.js +11 -2
- package/dist/lib/internal/cli-route-inventory.js +4 -2
- package/dist/lib/internal/codex-lifecycle.js +6 -6
- package/dist/lib/internal/demo-promotion.js +322 -0
- package/dist/lib/internal/demo-transcript.js +454 -0
- package/dist/lib/internal/host-control.js +28 -0
- package/dist/lib/internal/manual-validation.js +331 -0
- package/dist/lib/internal/platform-pr.js +18 -4
- package/dist/lib/internal/release-workflow.js +63 -84
- package/dist/lib/internal/sandbox-control.js +149 -2
- package/dist/lib/internal/task-artifact.js +16 -263
- package/dist/lib/internal/task-create.js +6 -5
- package/dist/lib/internal/task-event.js +37 -37
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-operation-registry.js +101 -4
- package/dist/lib/internal/task-review.js +11 -56
- package/dist/lib/internal/task-validate.js +30 -10
- package/dist/lib/owned-path.js +33 -0
- package/dist/lib/platform/check-status.js +11 -0
- package/dist/lib/platform/comment-safety.js +4 -1
- package/dist/lib/platform/completion-backfill.js +1 -1
- package/dist/lib/platform/github-data.js +241 -0
- package/dist/lib/platform/github-provider.js +15 -20
- package/dist/lib/platform/issue-comments.js +18 -12
- package/dist/lib/platform/issues.js +2 -180
- package/dist/lib/platform/pr-change-report.js +5 -4
- package/dist/lib/platform/pr-checks.js +12 -65
- package/dist/lib/platform/pr-summary.js +117 -33
- package/dist/lib/platform/pull-requests.js +5 -108
- package/dist/lib/platform/snapshots.js +2 -0
- package/dist/lib/platform/task-identities.js +2 -9
- package/dist/lib/platform/verification-sync.js +132 -143
- package/dist/lib/pr-review/evidence-grading.js +4 -8
- package/dist/lib/release/workflow.js +1 -1
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/rm.js +19 -1511
- package/dist/lib/sandbox/control/audit.js +203 -0
- package/dist/lib/sandbox/control/client.js +93 -54
- package/dist/lib/sandbox/control/executor.js +129 -65
- package/dist/lib/sandbox/control/finalization-response.js +27 -0
- package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
- package/dist/lib/sandbox/control/lifecycle.js +36 -2
- package/dist/lib/sandbox/control/native-file-lock.js +6 -98
- package/dist/lib/sandbox/control/protocol.js +55 -4
- package/dist/lib/sandbox/control/server.js +518 -127
- package/dist/lib/sandbox/control/state.js +64 -0
- package/dist/lib/sandbox/control/task-workflow.js +74 -0
- package/dist/lib/sandbox/control/workflow-executor.js +89 -0
- package/dist/lib/sandbox/index.js +1 -2
- package/dist/lib/sandbox/recovery.js +28 -4
- package/dist/lib/sandbox/removal.js +822 -0
- package/dist/lib/sandbox/task-cleanup.js +217 -0
- package/dist/lib/sandbox/workspace-identity.js +17 -1
- package/dist/lib/sandbox/workspace-view.js +13 -14
- package/dist/lib/task/activity-intent.js +2 -1
- package/dist/lib/task/activity-log.js +30 -24
- package/dist/lib/task/artifact-command.js +79 -0
- package/dist/lib/task/artifact-lifecycle.js +30 -28
- package/dist/lib/task/artifact-name.js +36 -0
- package/dist/lib/task/artifact-operations.js +29 -289
- package/dist/lib/task/artifact-receipts.js +3 -15
- package/dist/lib/task/artifact-recovery.js +430 -0
- package/dist/lib/task/artifact-repair-intent.js +134 -29
- package/dist/lib/task/artifact-schema.js +6 -14
- package/dist/lib/task/capabilities.js +12 -19
- package/dist/lib/task/codex-orchestration.js +71 -25
- package/dist/lib/task/commit-identity.js +28 -0
- package/dist/lib/task/commit-operation.js +33 -20
- package/dist/lib/task/control-authority.js +403 -6
- package/dist/lib/task/control-recovery.js +140 -0
- package/dist/lib/task/create-service.js +1 -8
- package/dist/lib/task/create.js +62 -9
- package/dist/lib/task/decision-details.js +2 -46
- package/dist/lib/task/delegation-receipts.js +64 -3
- package/dist/lib/task/events.js +275 -22
- package/dist/lib/task/finalization-state.js +9 -0
- package/dist/lib/task/human-override.js +8 -14
- package/dist/lib/task/implementation-inputs.js +3 -3
- package/dist/lib/task/intermediate-cleanup.js +441 -0
- package/dist/lib/task/invalidation.js +18 -65
- package/dist/lib/task/ledger-intents.js +7 -22
- package/dist/lib/task/ledger.js +1 -3
- package/dist/lib/task/lifecycle-recovery.js +549 -0
- package/dist/lib/task/lifecycle.js +31 -4
- package/dist/lib/task/local-artifact-finalization.js +197 -197
- package/dist/lib/task/manual-validation-completion.js +26 -0
- package/dist/lib/task/manual-validation-evidence.js +130 -0
- package/dist/lib/task/manual-validation-receipt.js +105 -0
- package/dist/lib/task/manual-validation-shared.js +40 -0
- package/dist/lib/task/manual-validation-transaction.js +243 -0
- package/dist/lib/task/markdown.js +47 -0
- package/dist/lib/task/orchestration.js +66 -19
- package/dist/lib/task/pr-delivery-fact.js +12 -56
- package/dist/lib/task/qualification-audit.js +20 -34
- package/dist/lib/task/recovery-warning.js +19 -0
- package/dist/lib/task/review-artifacts.js +2 -18
- package/dist/lib/task/review-command.js +26 -0
- package/dist/lib/task/review-finalization.js +175 -216
- package/dist/lib/task/review-fingerprint.js +2 -2
- package/dist/lib/task/sections.js +34 -56
- package/dist/lib/task/short-id.js +15 -7
- package/dist/lib/task/task-execution-lock.js +1 -1
- package/dist/lib/task/verification-engine.js +119 -337
- package/dist/lib/task/verification.js +1 -1
- package/dist/lib/task/workflow-command.js +16 -0
- package/dist/lib/task/workflow-warning-intents.js +0 -16
- package/dist/lib/task/workflow-warnings.js +28 -2
- package/dist/lib/update.js +2 -0
- package/dist/lib/version.js +1 -11
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +10 -4
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
- package/lib/fs/durable-write.ts +30 -0
- package/lib/fs/file-lock.ts +113 -0
- package/lib/host-control/audit.ts +22 -0
- package/lib/host-control/client.ts +172 -0
- package/lib/host-control/command.ts +60 -0
- package/lib/host-control/path.ts +167 -0
- package/lib/host-control/secure-fs.ts +135 -0
- package/lib/host-control/server.ts +180 -0
- package/lib/host-control/service.ts +133 -0
- package/lib/init.ts +15 -2
- package/lib/internal/cli-route-inventory.ts +4 -2
- package/lib/internal/codex-lifecycle.ts +6 -6
- package/lib/internal/demo-promotion.ts +354 -0
- package/lib/internal/demo-transcript.ts +419 -0
- package/lib/internal/host-control.ts +28 -0
- package/lib/internal/manual-validation.ts +315 -0
- package/lib/internal/platform-pr.ts +15 -4
- package/lib/internal/release-workflow.ts +74 -82
- package/lib/internal/sandbox-control.ts +162 -1
- package/lib/internal/task-artifact.ts +14 -217
- package/lib/internal/task-create.ts +5 -5
- package/lib/internal/task-event.ts +20 -11
- package/lib/internal/task-lifecycle.ts +4 -3
- package/lib/internal/task-operation-registry.ts +111 -4
- package/lib/internal/task-review.ts +9 -54
- package/lib/internal/task-validate.ts +26 -10
- package/lib/owned-path.ts +26 -0
- package/lib/platform/check-status.ts +9 -0
- package/lib/platform/comment-safety.ts +5 -0
- package/lib/platform/completion-backfill.ts +1 -1
- package/lib/platform/github-data.ts +322 -0
- package/lib/platform/github-provider.ts +15 -20
- package/lib/platform/issue-comments.ts +21 -14
- package/lib/platform/issues.ts +5 -246
- package/lib/platform/pr-change-report.ts +7 -5
- package/lib/platform/pr-checks.ts +13 -77
- package/lib/platform/pr-summary.ts +123 -35
- package/lib/platform/pull-requests.ts +6 -173
- package/lib/platform/snapshots.ts +53 -0
- package/lib/platform/task-identities.ts +3 -14
- package/lib/platform/verification-sync.ts +150 -156
- package/lib/pr-review/evidence-grading.ts +4 -9
- package/lib/release/workflow.ts +3 -3
- package/lib/sandbox/commands/create.ts +7 -6
- package/lib/sandbox/commands/rm.ts +21 -1895
- package/lib/sandbox/control/audit.ts +253 -0
- package/lib/sandbox/control/client.ts +109 -66
- package/lib/sandbox/control/executor.ts +134 -74
- package/lib/sandbox/control/finalization-response.ts +29 -0
- package/lib/sandbox/control/identity-sentinel.ts +137 -0
- package/lib/sandbox/control/lifecycle.ts +45 -3
- package/lib/sandbox/control/native-file-lock.ts +6 -107
- package/lib/sandbox/control/protocol.ts +80 -6
- package/lib/sandbox/control/server.ts +567 -132
- package/lib/sandbox/control/state.ts +86 -0
- package/lib/sandbox/control/task-workflow.ts +95 -0
- package/lib/sandbox/control/workflow-executor.ts +92 -0
- package/lib/sandbox/index.ts +1 -2
- package/lib/sandbox/recovery.ts +28 -5
- package/lib/sandbox/removal.ts +1142 -0
- package/lib/sandbox/task-cleanup.ts +209 -0
- package/lib/sandbox/workspace-identity.ts +19 -3
- package/lib/sandbox/workspace-view.ts +17 -15
- package/lib/task/activity-intent.ts +2 -2
- package/lib/task/activity-log.ts +27 -19
- package/lib/task/artifact-command.ts +85 -0
- package/lib/task/artifact-lifecycle.ts +31 -26
- package/lib/task/artifact-name.ts +55 -0
- package/lib/task/artifact-operations.ts +34 -310
- package/lib/task/artifact-receipts.ts +3 -14
- package/lib/task/artifact-recovery.ts +519 -0
- package/lib/task/artifact-repair-intent.ts +162 -33
- package/lib/task/artifact-schema.ts +5 -34
- package/lib/task/capabilities.ts +13 -22
- package/lib/task/codex-orchestration.ts +85 -24
- package/lib/task/commit-identity.ts +31 -0
- package/lib/task/commit-operation.ts +37 -20
- package/lib/task/control-authority.ts +540 -10
- package/lib/task/control-recovery.ts +186 -0
- package/lib/task/create-service.ts +1 -10
- package/lib/task/create.ts +63 -8
- package/lib/task/decision-details.ts +3 -71
- package/lib/task/delegation-receipts.ts +73 -2
- package/lib/task/events.ts +309 -24
- package/lib/task/finalization-state.ts +11 -0
- package/lib/task/human-override.ts +8 -16
- package/lib/task/implementation-inputs.ts +3 -3
- package/lib/task/intermediate-cleanup.ts +493 -0
- package/lib/task/invalidation.ts +18 -62
- package/lib/task/ledger-intents.ts +7 -24
- package/lib/task/ledger.ts +1 -3
- package/lib/task/lifecycle-recovery.ts +691 -0
- package/lib/task/lifecycle.ts +30 -5
- package/lib/task/local-artifact-finalization.ts +244 -234
- package/lib/task/manual-validation-completion.ts +40 -0
- package/lib/task/manual-validation-evidence.ts +209 -0
- package/lib/task/manual-validation-receipt.ts +156 -0
- package/lib/task/manual-validation-shared.ts +50 -0
- package/lib/task/manual-validation-transaction.ts +304 -0
- package/lib/task/markdown.ts +70 -0
- package/lib/task/orchestration.ts +84 -19
- package/lib/task/pr-delivery-fact.ts +15 -62
- package/lib/task/qualification-audit.ts +22 -33
- package/lib/task/recovery-warning.ts +21 -0
- package/lib/task/review-artifacts.ts +1 -20
- package/lib/task/review-command.ts +24 -0
- package/lib/task/review-finalization.ts +183 -292
- package/lib/task/review-fingerprint.ts +2 -2
- package/lib/task/sections.ts +33 -54
- package/lib/task/short-id.ts +27 -5
- package/lib/task/task-execution-lock.ts +1 -1
- package/lib/task/verification-engine.ts +113 -383
- package/lib/task/verification-types.ts +15 -0
- package/lib/task/verification.ts +1 -1
- package/lib/task/workflow-command.ts +17 -0
- package/lib/task/workflow-warning-intents.ts +0 -13
- package/lib/task/workflow-warnings.ts +21 -2
- package/lib/update.ts +6 -0
- package/lib/version.ts +1 -15
- package/package.json +6 -6
- package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
- package/templates/.agents/rules/create-issue.en.md +1 -1
- package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +4 -2
- package/templates/.agents/rules/issue-sync.zh-CN.md +4 -3
- package/templates/.agents/rules/local-artifact-repair.en.md +46 -39
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +43 -36
- package/templates/.agents/rules/testing-discipline.en.md +2 -0
- package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
- package/templates/.agents/scripts/package.json +3 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/config/verify.json +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +33 -21
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +33 -21
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
- package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/config/verify.json +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/create-task/config/verify.json +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/import-issue/config/verify.json +2 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/release/SKILL.en.md +7 -3
- package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
- package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/restore-task/config/verify.json +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
- package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
- package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
- package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/test/SKILL.en.md +17 -6
- package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
- package/templates/.agents/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.claude/commands/import-issue.en.md +1 -1
- package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
- package/templates/.claude/commands/restore-task.en.md +1 -1
- package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
- package/templates/.claude/commands/run-manual-validation.en.md +1 -1
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
- package/dist/lib/platform/adapters.js +0 -56
- package/lib/platform/adapters.ts +0 -197
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
export const HOST_CONTROL_DIRECTORY_MODE = 0o700;
|
|
6
|
+
export const HOST_CONTROL_SOCKET_MODE = 0o600;
|
|
7
|
+
export const HOST_CONTROL_WORKER_TOKEN_MODE = 0o600;
|
|
8
|
+
function currentPlatform() {
|
|
9
|
+
if (process.platform === 'linux' || process.platform === 'darwin')
|
|
10
|
+
return process.platform;
|
|
11
|
+
return 'unsupported';
|
|
12
|
+
}
|
|
13
|
+
function validUid(uid) {
|
|
14
|
+
const resolved = uid ?? process.getuid?.();
|
|
15
|
+
if (!Number.isInteger(resolved) || resolved === undefined || resolved < 0) {
|
|
16
|
+
throw new Error('HOST_CONTROL_ENDPOINT_INVALID: uid is unavailable');
|
|
17
|
+
}
|
|
18
|
+
return resolved;
|
|
19
|
+
}
|
|
20
|
+
function validUsername(username) {
|
|
21
|
+
const resolved = username ?? os.userInfo({ encoding: 'utf8' }).username;
|
|
22
|
+
if (!/^[A-Za-z0-9._-]+$/u.test(resolved)) {
|
|
23
|
+
throw new Error('HOST_CONTROL_ENDPOINT_INVALID: username is invalid');
|
|
24
|
+
}
|
|
25
|
+
return resolved;
|
|
26
|
+
}
|
|
27
|
+
export function resolveHostControlEndpoint(options = {}) {
|
|
28
|
+
const platform = options.platform ?? currentPlatform();
|
|
29
|
+
if (platform === 'linux')
|
|
30
|
+
return `/run/user/${validUid(options.uid)}/agent-infra/host-control.sock`;
|
|
31
|
+
if (platform === 'darwin')
|
|
32
|
+
return path.posix.join('/Users', validUsername(options.username), 'Library', 'Application Support', 'agent-infra', 'run', 'host-control.sock');
|
|
33
|
+
throw new Error('HOST_CONTROL_UNSUPPORTED_PLATFORM');
|
|
34
|
+
}
|
|
35
|
+
export function hostControlWorkerTokenPath(endpoint) {
|
|
36
|
+
return `${endpoint}.worker-token`;
|
|
37
|
+
}
|
|
38
|
+
function assertWorkerTokenFile(tokenPath, uid = process.getuid?.()) {
|
|
39
|
+
const stat = fs.lstatSync(tokenPath);
|
|
40
|
+
if (stat.isSymbolicLink() || !stat.isFile())
|
|
41
|
+
throw new Error('HOST_CONTROL_WORKER_TOKEN_INVALID');
|
|
42
|
+
if ((stat.mode & 0o777) !== HOST_CONTROL_WORKER_TOKEN_MODE)
|
|
43
|
+
throw new Error('HOST_CONTROL_WORKER_TOKEN_MODE_INVALID');
|
|
44
|
+
if (uid !== undefined && stat.uid !== uid)
|
|
45
|
+
throw new Error('HOST_CONTROL_WORKER_TOKEN_OWNER_INVALID');
|
|
46
|
+
const token = fs.readFileSync(tokenPath, 'utf8').trim();
|
|
47
|
+
if (!/^[a-f0-9]{64}$/u.test(token))
|
|
48
|
+
throw new Error('HOST_CONTROL_WORKER_TOKEN_INVALID');
|
|
49
|
+
return token;
|
|
50
|
+
}
|
|
51
|
+
export function ensureHostControlWorkerToken(endpoint, uid = process.getuid?.()) {
|
|
52
|
+
const tokenPath = hostControlWorkerTokenPath(endpoint);
|
|
53
|
+
try {
|
|
54
|
+
return assertWorkerTokenFile(tokenPath, uid);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (error.code !== 'ENOENT')
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
const token = randomBytes(32).toString('hex');
|
|
61
|
+
try {
|
|
62
|
+
const fd = fs.openSync(tokenPath, 'wx', HOST_CONTROL_WORKER_TOKEN_MODE);
|
|
63
|
+
try {
|
|
64
|
+
fs.writeFileSync(fd, `${token}\n`, 'utf8');
|
|
65
|
+
fs.fchmodSync(fd, HOST_CONTROL_WORKER_TOKEN_MODE);
|
|
66
|
+
if (uid !== undefined && process.getuid?.() === uid)
|
|
67
|
+
fs.fchownSync(fd, uid, process.getgid?.() ?? -1);
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
fs.closeSync(fd);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (error.code !== 'EEXIST')
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
return assertWorkerTokenFile(tokenPath, uid);
|
|
78
|
+
}
|
|
79
|
+
export function readHostControlWorkerToken(endpoint, uid = process.getuid?.()) {
|
|
80
|
+
return assertWorkerTokenFile(hostControlWorkerTokenPath(endpoint), uid);
|
|
81
|
+
}
|
|
82
|
+
export function removeHostControlWorkerToken(endpoint) {
|
|
83
|
+
try {
|
|
84
|
+
fs.unlinkSync(hostControlWorkerTokenPath(endpoint));
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (error.code !== 'ENOENT')
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export function inspectHostControlEndpoint(endpoint, expected = {}) {
|
|
92
|
+
let endpointStat;
|
|
93
|
+
let parentStat;
|
|
94
|
+
try {
|
|
95
|
+
endpointStat = fs.lstatSync(endpoint);
|
|
96
|
+
parentStat = fs.lstatSync(path.dirname(endpoint));
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return { ok: false, code: 'HOST_CONTROL_ENDPOINT_MISSING' };
|
|
100
|
+
}
|
|
101
|
+
if (endpointStat.isSymbolicLink())
|
|
102
|
+
return { ok: false, code: 'HOST_CONTROL_ENDPOINT_SYMLINK' };
|
|
103
|
+
if (!endpointStat.isSocket())
|
|
104
|
+
return { ok: false, code: 'HOST_CONTROL_ENDPOINT_NOT_SOCKET' };
|
|
105
|
+
if (!parentStat.isDirectory() || parentStat.isSymbolicLink()) {
|
|
106
|
+
return { ok: false, code: 'HOST_CONTROL_PARENT_INVALID' };
|
|
107
|
+
}
|
|
108
|
+
let ancestor = path.dirname(endpoint);
|
|
109
|
+
while (true) {
|
|
110
|
+
let stat;
|
|
111
|
+
try {
|
|
112
|
+
stat = fs.lstatSync(ancestor);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return { ok: false, code: 'HOST_CONTROL_PARENT_INVALID' };
|
|
116
|
+
}
|
|
117
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
118
|
+
return { ok: false, code: 'HOST_CONTROL_PARENT_INVALID' };
|
|
119
|
+
const next = path.dirname(ancestor);
|
|
120
|
+
if (next === ancestor)
|
|
121
|
+
break;
|
|
122
|
+
ancestor = next;
|
|
123
|
+
}
|
|
124
|
+
const mode = endpointStat.mode & 0o777;
|
|
125
|
+
const parentMode = parentStat.mode & 0o777;
|
|
126
|
+
if (mode !== HOST_CONTROL_SOCKET_MODE)
|
|
127
|
+
return { ok: false, code: 'HOST_CONTROL_ENDPOINT_MODE_INVALID', ownerUid: endpointStat.uid, mode, parentMode };
|
|
128
|
+
if (parentMode !== HOST_CONTROL_DIRECTORY_MODE)
|
|
129
|
+
return { ok: false, code: 'HOST_CONTROL_PARENT_MODE_INVALID', ownerUid: endpointStat.uid, mode, parentMode };
|
|
130
|
+
if (expected.uid !== undefined && endpointStat.uid !== expected.uid) {
|
|
131
|
+
return { ok: false, code: 'HOST_CONTROL_ENDPOINT_OWNER_INVALID', ownerUid: endpointStat.uid, mode, parentMode };
|
|
132
|
+
}
|
|
133
|
+
if (expected.uid !== undefined && parentStat.uid !== expected.uid) {
|
|
134
|
+
return { ok: false, code: 'HOST_CONTROL_PARENT_OWNER_INVALID', ownerUid: parentStat.uid, mode, parentMode };
|
|
135
|
+
}
|
|
136
|
+
return { ok: true, code: null, ownerUid: endpointStat.uid, mode, parentMode };
|
|
137
|
+
}
|
|
138
|
+
export function prepareHostControlDirectory(endpoint, uid = process.getuid?.()) {
|
|
139
|
+
const directory = path.dirname(endpoint);
|
|
140
|
+
const segments = path.resolve(directory).split(path.sep);
|
|
141
|
+
let current = path.isAbsolute(directory) ? path.sep : '';
|
|
142
|
+
for (const segment of segments.filter(Boolean)) {
|
|
143
|
+
current = path.join(current, segment);
|
|
144
|
+
try {
|
|
145
|
+
const stat = fs.lstatSync(current);
|
|
146
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
147
|
+
throw new Error('HOST_CONTROL_PARENT_INVALID');
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (error.code !== 'ENOENT')
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
fs.mkdirSync(directory, { recursive: true, mode: HOST_CONTROL_DIRECTORY_MODE });
|
|
155
|
+
fs.chmodSync(directory, HOST_CONTROL_DIRECTORY_MODE);
|
|
156
|
+
if (uid !== undefined && process.getuid?.() === uid)
|
|
157
|
+
fs.chownSync(directory, uid, process.getgid?.() ?? -1);
|
|
158
|
+
return directory;
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=path.js.map
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
const NO_FOLLOW = fs.constants.O_NOFOLLOW ?? 0;
|
|
5
|
+
const DIRECTORY = fs.constants.O_DIRECTORY ?? 0;
|
|
6
|
+
export class SecureFileError extends Error {
|
|
7
|
+
code;
|
|
8
|
+
constructor(code, message) {
|
|
9
|
+
super(`${code}: ${message}`);
|
|
10
|
+
this.name = 'SecureFileError';
|
|
11
|
+
this.code = code;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function conflict(message) {
|
|
15
|
+
throw new SecureFileError('TASK_ARTIFACT_WRITE_CONFLICT', message);
|
|
16
|
+
}
|
|
17
|
+
function sameStat(left, right) {
|
|
18
|
+
return left.dev === right.dev
|
|
19
|
+
&& left.ino === right.ino
|
|
20
|
+
&& left.size === right.size
|
|
21
|
+
&& left.mtimeNs === right.mtimeNs
|
|
22
|
+
&& left.ctimeNs === right.ctimeNs;
|
|
23
|
+
}
|
|
24
|
+
function assertNoSymlinkAncestors(candidate) {
|
|
25
|
+
let current = path.dirname(candidate);
|
|
26
|
+
while (true) {
|
|
27
|
+
const stat = fs.lstatSync(current);
|
|
28
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
29
|
+
conflict('candidate parent is not a real directory');
|
|
30
|
+
const parent = path.dirname(current);
|
|
31
|
+
if (parent === current)
|
|
32
|
+
return;
|
|
33
|
+
current = parent;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export async function readStableFile(candidate, options) {
|
|
37
|
+
return readStableFileSync(candidate, options);
|
|
38
|
+
}
|
|
39
|
+
/** Shared bounded reader for synchronous domain commands and async workflow callers. */
|
|
40
|
+
export function readStableFileSync(candidate, options) {
|
|
41
|
+
if (!path.isAbsolute(candidate)) {
|
|
42
|
+
conflict('candidate path must be absolute and terminal');
|
|
43
|
+
}
|
|
44
|
+
assertNoSymlinkAncestors(candidate);
|
|
45
|
+
let descriptor;
|
|
46
|
+
try {
|
|
47
|
+
// O_NONBLOCK lets fstat reject a FIFO even when it has no writer.
|
|
48
|
+
descriptor = fs.openSync(candidate, fs.constants.O_RDONLY | NO_FOLLOW | (fs.constants.O_NONBLOCK ?? 0));
|
|
49
|
+
const before = fs.fstatSync(descriptor, { bigint: true });
|
|
50
|
+
if (!before.isFile())
|
|
51
|
+
conflict('candidate is not a regular file');
|
|
52
|
+
if (before.size > BigInt(options.maxBytes))
|
|
53
|
+
conflict('candidate exceeds the bounded read limit');
|
|
54
|
+
const bytes = Buffer.alloc(Number(before.size));
|
|
55
|
+
let offset = 0;
|
|
56
|
+
while (offset < bytes.length) {
|
|
57
|
+
const bytesRead = fs.readSync(descriptor, bytes, offset, bytes.length - offset, offset);
|
|
58
|
+
if (bytesRead === 0)
|
|
59
|
+
conflict('candidate reached EOF before the bounded read completed');
|
|
60
|
+
offset += bytesRead;
|
|
61
|
+
}
|
|
62
|
+
const after = fs.fstatSync(descriptor, { bigint: true });
|
|
63
|
+
if (!sameStat(before, after))
|
|
64
|
+
conflict('candidate identity or metadata changed during read');
|
|
65
|
+
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
66
|
+
if (options.expectedSha256 !== undefined && sha256 !== options.expectedSha256) {
|
|
67
|
+
conflict('candidate digest does not match the expected digest');
|
|
68
|
+
}
|
|
69
|
+
return { bytes, sha256, stat: after };
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (error instanceof SecureFileError)
|
|
73
|
+
throw error;
|
|
74
|
+
const code = error.code;
|
|
75
|
+
if (code === 'ELOOP' || code === 'ENOTSUP' || code === 'EINVAL') {
|
|
76
|
+
conflict('candidate terminal is not a stable non-symlink file');
|
|
77
|
+
}
|
|
78
|
+
throw new SecureFileError('TASK_ARTIFACT_WRITE_CONFLICT', error instanceof Error ? error.message : String(error));
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
if (descriptor !== undefined)
|
|
82
|
+
fs.closeSync(descriptor);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export async function writeAtomicFile(target, bytes, mode = 0o600, expectedSha256) {
|
|
86
|
+
const parent = path.dirname(target);
|
|
87
|
+
let current = path.resolve(parent);
|
|
88
|
+
while (true) {
|
|
89
|
+
let stat;
|
|
90
|
+
try {
|
|
91
|
+
stat = await fs.promises.lstat(current);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
throw new SecureFileError('TASK_ARTIFACT_WRITE_CONFLICT', 'authoritative parent directory is missing');
|
|
95
|
+
}
|
|
96
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
97
|
+
throw new SecureFileError('TASK_ARTIFACT_WRITE_CONFLICT', 'authoritative parent directory is not a real directory');
|
|
98
|
+
}
|
|
99
|
+
const next = path.dirname(current);
|
|
100
|
+
if (next === current)
|
|
101
|
+
break;
|
|
102
|
+
current = next;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const existing = await fs.promises.lstat(target);
|
|
106
|
+
if (existing.isSymbolicLink() || !existing.isFile()) {
|
|
107
|
+
throw new SecureFileError('TASK_ARTIFACT_WRITE_CONFLICT', 'authoritative target is not a regular file');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (error.code !== 'ENOENT')
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
const temporary = path.join(parent, `.${path.basename(target)}.${process.pid}.${Date.now()}.tmp`);
|
|
115
|
+
let handle;
|
|
116
|
+
try {
|
|
117
|
+
handle = await fs.promises.open(temporary, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, mode);
|
|
118
|
+
await handle.writeFile(bytes);
|
|
119
|
+
await handle.sync();
|
|
120
|
+
await handle.chmod(mode);
|
|
121
|
+
await handle.close();
|
|
122
|
+
handle = undefined;
|
|
123
|
+
// Check after all asynchronous preparation; keep the final check/rename adjacent.
|
|
124
|
+
if (expectedSha256 !== undefined)
|
|
125
|
+
readStableFileSync(target, { maxBytes: 1024 * 1024, expectedSha256 });
|
|
126
|
+
fs.renameSync(temporary, target);
|
|
127
|
+
const directory = await fs.promises.open(parent, fs.constants.O_RDONLY | DIRECTORY);
|
|
128
|
+
try {
|
|
129
|
+
await directory.sync();
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
await directory.close();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
await handle?.close().catch(() => undefined);
|
|
137
|
+
await fs.promises.rm(temporary, { force: true }).catch(() => undefined);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=secure-fs.js.map
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import net from 'node:net';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { HOST_CONTROL_DIRECTORY_MODE, HOST_CONTROL_SOCKET_MODE, ensureHostControlWorkerToken, inspectHostControlEndpoint, prepareHostControlDirectory, removeHostControlWorkerToken, resolveHostControlEndpoint } from './path.js';
|
|
5
|
+
import { HOST_CONTROL_MAX_REQUEST_BYTES, validateHostControlRequest } from './client.js';
|
|
6
|
+
import { acquireFileLock } from '../fs/file-lock.js';
|
|
7
|
+
import { appendHostControlAudit } from './audit.js';
|
|
8
|
+
function auditFor(request, phase, outcome) {
|
|
9
|
+
return {
|
|
10
|
+
requestId: request.id,
|
|
11
|
+
taskId: request.taskId,
|
|
12
|
+
operation: request.operation,
|
|
13
|
+
scope: request.scope,
|
|
14
|
+
phase,
|
|
15
|
+
outcome,
|
|
16
|
+
authorityKind: 'host-broker',
|
|
17
|
+
transportKind: 'host-fixed-transport',
|
|
18
|
+
identityDigest: createHash('sha256').update(`${request.taskId ?? '-'}\0${request.generation ?? '-'}`, 'utf8').digest('hex')
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function failedResponse(id, status, code, message) {
|
|
22
|
+
const error = { code, message };
|
|
23
|
+
const stdout = `${JSON.stringify({ status: 'failed', changed: status === 'rejected' ? false : null, error })}\n`;
|
|
24
|
+
return { version: 1, id, status, exitCode: 1, stdout, stderr: '', error };
|
|
25
|
+
}
|
|
26
|
+
async function handleConnection(socket, options) {
|
|
27
|
+
await new Promise((resolve) => {
|
|
28
|
+
let input = '';
|
|
29
|
+
let completed = false;
|
|
30
|
+
let processing = false;
|
|
31
|
+
socket.setEncoding('utf8');
|
|
32
|
+
// A disconnected caller does not cancel an already dispatched domain operation.
|
|
33
|
+
socket.on('error', () => socket.destroy());
|
|
34
|
+
socket.on('close', () => { if (!processing)
|
|
35
|
+
resolve(); });
|
|
36
|
+
socket.on('end', () => { if (!processing)
|
|
37
|
+
socket.end(); });
|
|
38
|
+
const finish = (value) => {
|
|
39
|
+
if (completed || socket.destroyed)
|
|
40
|
+
return;
|
|
41
|
+
completed = true;
|
|
42
|
+
socket.end(`${JSON.stringify(value)}\n`);
|
|
43
|
+
};
|
|
44
|
+
socket.on('data', async (chunk) => {
|
|
45
|
+
if (completed || processing)
|
|
46
|
+
return;
|
|
47
|
+
input += chunk;
|
|
48
|
+
if (Buffer.byteLength(input, 'utf8') > HOST_CONTROL_MAX_REQUEST_BYTES) {
|
|
49
|
+
finish(failedResponse('invalid', 'rejected', 'HOST_CONTROL_REQUEST_TOO_LARGE', 'request exceeds the control limit'));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const newline = input.indexOf('\n');
|
|
53
|
+
if (newline < 0)
|
|
54
|
+
return;
|
|
55
|
+
const raw = input.slice(0, newline);
|
|
56
|
+
let request;
|
|
57
|
+
try {
|
|
58
|
+
request = validateHostControlRequest(JSON.parse(raw));
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
finish(failedResponse('invalid', 'rejected', 'HOST_CONTROL_REQUEST_INVALID', error instanceof Error ? error.message : String(error)));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
processing = true;
|
|
65
|
+
let started = false;
|
|
66
|
+
try {
|
|
67
|
+
options.audit?.(auditFor(request, 'accepted', 'in-progress'));
|
|
68
|
+
started = true;
|
|
69
|
+
const result = await options.dispatch(request);
|
|
70
|
+
options.audit?.(auditFor(request, 'completed', result.exitCode === 0 ? 'success' : 'failure'));
|
|
71
|
+
finish({ version: 1, id: request.id, status: 'completed', ...result, error: null });
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
75
|
+
const code = /^([A-Z][A-Z0-9_]+)/u.exec(message)?.[1] ?? 'HOST_CONTROL_DISPATCH_FAILED';
|
|
76
|
+
const status = started ? 'unknown' : 'rejected';
|
|
77
|
+
try {
|
|
78
|
+
options.audit?.(auditFor(request, started ? 'completed' : 'rejected', status));
|
|
79
|
+
}
|
|
80
|
+
catch { /* Return the uncertainty even when the audit sink is unavailable. */ }
|
|
81
|
+
finish(failedResponse(request.id, status, code, message));
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
resolve();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
export async function startHostControlServer(options) {
|
|
90
|
+
const endpoint = options.endpoint ?? resolveHostControlEndpoint();
|
|
91
|
+
const audit = options.audit ?? ((entry) => appendHostControlAudit(endpoint, entry));
|
|
92
|
+
prepareHostControlDirectory(endpoint);
|
|
93
|
+
const lock = acquireFileLock(`${endpoint}.lock`);
|
|
94
|
+
let server;
|
|
95
|
+
try {
|
|
96
|
+
ensureHostControlWorkerToken(endpoint);
|
|
97
|
+
try {
|
|
98
|
+
const existing = fs.lstatSync(endpoint);
|
|
99
|
+
if (existing.isSymbolicLink() || !existing.isSocket())
|
|
100
|
+
throw new Error('HOST_CONTROL_ENDPOINT_INVALID');
|
|
101
|
+
fs.unlinkSync(endpoint);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
if (error.code !== 'ENOENT')
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
const connections = new Set();
|
|
108
|
+
server = net.createServer({ allowHalfOpen: true }, (socket) => {
|
|
109
|
+
const connection = handleConnection(socket, { ...options, audit });
|
|
110
|
+
connections.add(connection);
|
|
111
|
+
void connection.finally(() => connections.delete(connection));
|
|
112
|
+
});
|
|
113
|
+
const listeningServer = server;
|
|
114
|
+
await new Promise((resolve, reject) => {
|
|
115
|
+
listeningServer.once('error', reject);
|
|
116
|
+
listeningServer.listen(endpoint, () => { listeningServer.off('error', reject); resolve(); });
|
|
117
|
+
});
|
|
118
|
+
fs.chmodSync(endpoint, HOST_CONTROL_SOCKET_MODE);
|
|
119
|
+
const inspection = inspectHostControlEndpoint(endpoint, { uid: process.getuid?.() });
|
|
120
|
+
if (!inspection.ok) {
|
|
121
|
+
await new Promise((resolve) => listeningServer.close(() => resolve()));
|
|
122
|
+
removeHostControlWorkerToken(endpoint);
|
|
123
|
+
throw new Error(inspection.code ?? 'HOST_CONTROL_ENDPOINT_INVALID');
|
|
124
|
+
}
|
|
125
|
+
let closing;
|
|
126
|
+
return {
|
|
127
|
+
endpoint,
|
|
128
|
+
server,
|
|
129
|
+
close: () => closing ??= (async () => {
|
|
130
|
+
try {
|
|
131
|
+
await new Promise((resolve) => listeningServer.close(() => resolve()));
|
|
132
|
+
await Promise.all(connections);
|
|
133
|
+
await fs.promises.rm(endpoint, { force: true });
|
|
134
|
+
removeHostControlWorkerToken(endpoint);
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
lock.release();
|
|
138
|
+
}
|
|
139
|
+
})()
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
if (server?.listening)
|
|
144
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
145
|
+
lock.release();
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
export async function serveHostControl(options, signal) {
|
|
150
|
+
const running = await startHostControlServer(options);
|
|
151
|
+
if (!signal)
|
|
152
|
+
return;
|
|
153
|
+
if (signal.aborted) {
|
|
154
|
+
await running.close();
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
await new Promise((resolve) => signal.addEventListener('abort', () => { void running.close().then(resolve); }, { once: true }));
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { inspectHostControlEndpoint, resolveHostControlEndpoint } from './path.js';
|
|
6
|
+
import { serveHostControl, startHostControlServer } from './server.js';
|
|
7
|
+
function servicePlatform() {
|
|
8
|
+
if (process.platform === 'linux' || process.platform === 'darwin')
|
|
9
|
+
return process.platform;
|
|
10
|
+
return 'unsupported';
|
|
11
|
+
}
|
|
12
|
+
function serviceEntryPoint() {
|
|
13
|
+
const compiled = fileURLToPath(new URL('../../bin/internal-cli.js', import.meta.url));
|
|
14
|
+
if (fs.existsSync(compiled))
|
|
15
|
+
return fs.realpathSync.native(compiled);
|
|
16
|
+
const candidate = process.argv[1];
|
|
17
|
+
if (candidate && fs.existsSync(candidate))
|
|
18
|
+
return fs.realpathSync.native(candidate);
|
|
19
|
+
return fs.realpathSync.native(fileURLToPath(new URL('../../bin/internal-cli.ts', import.meta.url)));
|
|
20
|
+
}
|
|
21
|
+
function xml(value) {
|
|
22
|
+
return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"');
|
|
23
|
+
}
|
|
24
|
+
function systemdArgument(value) {
|
|
25
|
+
const escaped = value.replace(/[\\"]/g, '\\$&')
|
|
26
|
+
.replace(/[\x00-\x1f\x7f]/g, (character) => `\\x${character.charCodeAt(0).toString(16).padStart(2, '0')}`)
|
|
27
|
+
.replaceAll('%', '%%');
|
|
28
|
+
return `"${escaped}"`;
|
|
29
|
+
}
|
|
30
|
+
function servicePaths(platform = servicePlatform()) {
|
|
31
|
+
if (platform === 'linux')
|
|
32
|
+
return { unit: path.join(os.homedir(), '.config', 'systemd', 'user', 'agent-infra-host-control.service') };
|
|
33
|
+
if (platform === 'darwin')
|
|
34
|
+
return { plist: path.join(os.homedir(), 'Library', 'LaunchAgents', 'com.fitlab-ai.agent-infra.host-control.plist') };
|
|
35
|
+
throw new Error('HOST_CONTROL_UNSUPPORTED_PLATFORM');
|
|
36
|
+
}
|
|
37
|
+
export function installHostControlService(platform = servicePlatform()) {
|
|
38
|
+
const paths = servicePaths(platform);
|
|
39
|
+
const entry = serviceEntryPoint();
|
|
40
|
+
if (paths.unit) {
|
|
41
|
+
// mkdir only applies mode to directories it creates, and this one usually
|
|
42
|
+
// already exists, so restate the intended mode explicitly.
|
|
43
|
+
fs.mkdirSync(path.dirname(paths.unit), { recursive: true, mode: 0o700 });
|
|
44
|
+
fs.chmodSync(path.dirname(paths.unit), 0o700);
|
|
45
|
+
fs.writeFileSync(paths.unit, [
|
|
46
|
+
'[Unit]',
|
|
47
|
+
'Description=agent-infra host-control service',
|
|
48
|
+
'',
|
|
49
|
+
'[Service]',
|
|
50
|
+
// The ':' executable prefix disables environment expansion for literal paths.
|
|
51
|
+
`ExecStart=${systemdArgument(`:${process.execPath}`)} ${systemdArgument(entry)} host-control serve`,
|
|
52
|
+
'Restart=on-failure',
|
|
53
|
+
'NoNewPrivileges=true',
|
|
54
|
+
// Workers receive host paths, including working directories and inputs under /tmp.
|
|
55
|
+
'PrivateTmp=false',
|
|
56
|
+
'',
|
|
57
|
+
'[Install]',
|
|
58
|
+
'WantedBy=default.target',
|
|
59
|
+
''
|
|
60
|
+
].join('\n'), { mode: 0o600 });
|
|
61
|
+
fs.chmodSync(paths.unit, 0o600);
|
|
62
|
+
return paths.unit;
|
|
63
|
+
}
|
|
64
|
+
const plist = paths.plist;
|
|
65
|
+
fs.mkdirSync(path.dirname(plist), { recursive: true, mode: 0o700 });
|
|
66
|
+
fs.chmodSync(path.dirname(plist), 0o700);
|
|
67
|
+
const argumentsXml = [process.execPath, entry, 'host-control', 'serve']
|
|
68
|
+
.map((argument) => ` <string>${xml(argument)}</string>`)
|
|
69
|
+
.join('\n');
|
|
70
|
+
fs.writeFileSync(plist, [
|
|
71
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
72
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
73
|
+
'<plist version="1.0">',
|
|
74
|
+
'<dict>',
|
|
75
|
+
' <key>Label</key>',
|
|
76
|
+
' <string>com.fitlab-ai.agent-infra.host-control</string>',
|
|
77
|
+
' <key>ProgramArguments</key>',
|
|
78
|
+
' <array>',
|
|
79
|
+
argumentsXml,
|
|
80
|
+
' </array>',
|
|
81
|
+
' <key>RunAtLoad</key>',
|
|
82
|
+
' <true/>',
|
|
83
|
+
' <key>KeepAlive</key>',
|
|
84
|
+
' <true/>',
|
|
85
|
+
'</dict>',
|
|
86
|
+
'</plist>',
|
|
87
|
+
''
|
|
88
|
+
].join('\n'), { mode: 0o600 });
|
|
89
|
+
fs.chmodSync(plist, 0o600);
|
|
90
|
+
return plist;
|
|
91
|
+
}
|
|
92
|
+
export function uninstallHostControlService(platform = servicePlatform()) {
|
|
93
|
+
const paths = servicePaths(platform);
|
|
94
|
+
const target = paths.unit ?? paths.plist;
|
|
95
|
+
fs.rmSync(target, { force: true });
|
|
96
|
+
return target;
|
|
97
|
+
}
|
|
98
|
+
export function hostControlServiceEndpoint(options = {}) {
|
|
99
|
+
return options.endpoint ?? resolveHostControlEndpoint(options);
|
|
100
|
+
}
|
|
101
|
+
export async function startHostControlService(options) {
|
|
102
|
+
return startHostControlServer({ endpoint: hostControlServiceEndpoint(options), dispatch: options.dispatch, ...(options.audit ? { audit: options.audit } : {}) });
|
|
103
|
+
}
|
|
104
|
+
export async function serveHostControlService(options, signal) {
|
|
105
|
+
await serveHostControl({ endpoint: hostControlServiceEndpoint(options), dispatch: options.dispatch, ...(options.audit ? { audit: options.audit } : {}) }, signal);
|
|
106
|
+
}
|
|
107
|
+
export function hostControlServiceStatus(options = {}) {
|
|
108
|
+
const endpoint = hostControlServiceEndpoint(options);
|
|
109
|
+
const result = inspectHostControlEndpoint(endpoint, { uid: options.uid ?? process.getuid?.() });
|
|
110
|
+
return { endpoint, available: result.ok, code: result.code };
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=service.js.map
|
package/dist/lib/init.js
CHANGED
|
@@ -17,6 +17,7 @@ const PLATFORM_DEFAULT_ENGINES = Object.freeze({
|
|
|
17
17
|
darwin: 'colima',
|
|
18
18
|
win32: 'wsl2'
|
|
19
19
|
});
|
|
20
|
+
const DEMO_PLATFORM_ENV = 'AGENT_INFRA_DEMO_PLATFORM';
|
|
20
21
|
function detectProjectName() {
|
|
21
22
|
try {
|
|
22
23
|
const url = execSync('git remote get-url origin', { stdio: ['pipe', 'pipe', 'pipe'] })
|
|
@@ -45,6 +46,12 @@ function detectOrgName() {
|
|
|
45
46
|
}
|
|
46
47
|
return '';
|
|
47
48
|
}
|
|
49
|
+
function resolveInitPlatform() {
|
|
50
|
+
const override = process.env[DEMO_PLATFORM_ENV];
|
|
51
|
+
if (override === 'linux' || override === 'darwin' || override === 'win32')
|
|
52
|
+
return override;
|
|
53
|
+
return platform();
|
|
54
|
+
}
|
|
48
55
|
const VALID_NAME_RE = /^[a-zA-Z0-9_.@-]+$/;
|
|
49
56
|
function parseLocalSources(input) {
|
|
50
57
|
return input
|
|
@@ -109,7 +116,7 @@ async function cmdInit() {
|
|
|
109
116
|
process.exitCode = 1;
|
|
110
117
|
return;
|
|
111
118
|
}
|
|
112
|
-
const currentPlatform =
|
|
119
|
+
const currentPlatform = resolveInitPlatform();
|
|
113
120
|
const defaultEngine = PLATFORM_DEFAULT_ENGINES[currentPlatform];
|
|
114
121
|
const engineChoices = enginesForPlatform(currentPlatform).sort((left, right) => {
|
|
115
122
|
if (left === defaultEngine)
|
|
@@ -207,6 +214,8 @@ async function cmdInit() {
|
|
|
207
214
|
ok('Installed .agents/skills/update-agent-infra/');
|
|
208
215
|
renderFile(path.join(templateDir, '.agents', 'scripts', 'lib', 'agent-infra-package.js'), path.join('.agents', 'scripts', 'lib', 'agent-infra-package.js'), replacements);
|
|
209
216
|
ok('Installed .agents/scripts/lib/agent-infra-package.js');
|
|
217
|
+
renderFile(path.join(templateDir, '.agents', 'scripts', 'package.json'), path.join('.agents', 'scripts', 'package.json'), replacements);
|
|
218
|
+
ok('Installed .agents/scripts/package.json');
|
|
210
219
|
const reconcileResult = applyAgentClientReconciliation(workflowPlan);
|
|
211
220
|
for (const target of reconcileResult.applied)
|
|
212
221
|
ok(`Installed ${target}`);
|
|
@@ -235,5 +244,5 @@ async function cmdInit() {
|
|
|
235
244
|
console.log('');
|
|
236
245
|
}
|
|
237
246
|
}
|
|
238
|
-
export { cmdInit };
|
|
247
|
+
export { cmdInit, resolveInitPlatform };
|
|
239
248
|
//# sourceMappingURL=init.js.map
|
|
@@ -33,6 +33,7 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
|
|
|
33
33
|
'task-create': ['input'],
|
|
34
34
|
'task-qualification': ['proposal', 'confirm', 'supersede', 'revoke'],
|
|
35
35
|
'sandbox-control': ['serve', 'execute', 'recover', 'client'],
|
|
36
|
+
'host-control': ['serve', 'status', 'install', 'uninstall'],
|
|
36
37
|
'agent-client': ['next-steps', 'model-selection'],
|
|
37
38
|
'codex-lifecycle': ['preflight', 'capability-arm', 'hook-event', 'resolve-start', 'resolve-stop', 'consume'],
|
|
38
39
|
'codex-sandbox-controller': ['verify-context', 'run'],
|
|
@@ -53,7 +54,7 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
|
|
|
53
54
|
'task-ledger': ['decision-next-id', 'stage-status', 'finding-upsert', 'finding-respond', 'finding-review', 'decision-upsert', 'rework-intent-upsert'],
|
|
54
55
|
'task-warning': ['list', 'add', 'set-status'],
|
|
55
56
|
'task-activity': ['pr-review-inspect', 'pr-review-start', 'pr-review-complete', 'pr-review-terminate'],
|
|
56
|
-
'task-artifact': ['inspect', 'init', '
|
|
57
|
+
'task-artifact': ['inspect', 'init', 'finalize-local'],
|
|
57
58
|
'task-orchestration': ['status', 'progress'],
|
|
58
59
|
'task-review': ['finalize-summary'],
|
|
59
60
|
'task-event': ['event'],
|
|
@@ -64,7 +65,8 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
|
|
|
64
65
|
'task-short-id': ['list', 'list-verify', 'alloc', 'release', 'resolve'],
|
|
65
66
|
'task-snapshot': ['snapshot'],
|
|
66
67
|
'task-verify': ['event'],
|
|
67
|
-
'task-validate': ['snapshot', 'inplace']
|
|
68
|
+
'task-validate': ['snapshot', 'inplace'],
|
|
69
|
+
'manual-validation': ['transaction']
|
|
68
70
|
});
|
|
69
71
|
export const INTERNAL_CLI_ROUTE_SELECTORS = INTERNAL_HANDLER_ROUTE_SELECTORS;
|
|
70
72
|
function first(args) {
|
|
@@ -191,7 +191,7 @@ async function codexLifecycle(args = []) {
|
|
|
191
191
|
output({
|
|
192
192
|
status: 'armed',
|
|
193
193
|
changed: true,
|
|
194
|
-
|
|
194
|
+
capabilityRef: armed.capabilityRef,
|
|
195
195
|
marker: armed.marker,
|
|
196
196
|
expiresAt: armed.expiresAt,
|
|
197
197
|
buildIdentity: armed.buildIdentity,
|
|
@@ -210,12 +210,12 @@ async function codexLifecycle(args = []) {
|
|
|
210
210
|
}
|
|
211
211
|
const payload = await readStdin();
|
|
212
212
|
if (phase === 'post-tool') {
|
|
213
|
-
const
|
|
214
|
-
if (
|
|
213
|
+
const capabilityRef = payloadText(payload, 'capabilityRef');
|
|
214
|
+
if (capabilityRef) {
|
|
215
215
|
const capabilityStore = createCodexCapabilityStore();
|
|
216
|
-
const armed = capabilityStore.
|
|
217
|
-
const capability =
|
|
218
|
-
|
|
216
|
+
const armed = capabilityStore.inspectReference(capabilityRef);
|
|
217
|
+
const capability = capabilityStore.attestByReference({
|
|
218
|
+
capabilityRef,
|
|
219
219
|
sessionId: payloadText(payload, 'sessionId'),
|
|
220
220
|
turnId: payloadText(payload, 'turnId'),
|
|
221
221
|
toolUseId: payloadText(payload, 'toolUseId'),
|