@fitlab-ai/agent-infra 0.10.0 → 0.11.1
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 +2 -6
- package/README.zh-CN.md +2 -6
- package/bin/internal-cli.sh +0 -13
- package/dist/bin/internal-cli.js +11 -49
- package/dist/lib/internal/cli-route-inventory.js +3 -4
- package/dist/lib/internal/manual-validation.js +6 -2
- package/dist/lib/internal/platform-comment.js +60 -2
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-finalization.js +2 -2
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-operation-registry.js +22 -12
- package/dist/lib/internal/task-orchestration.js +2 -2
- package/dist/lib/internal/task-review.js +10 -5
- package/dist/lib/platform/activity-recovery.js +159 -0
- package/dist/lib/platform/issue-comments.js +84 -32
- package/dist/lib/platform/issues.js +3 -4
- package/dist/lib/platform/task-comment-projection.js +70 -0
- package/dist/lib/platform/task-recovery.js +55 -0
- package/dist/lib/platform/verification-sync.js +184 -162
- package/dist/lib/sandbox/control/executor.js +4 -2
- package/dist/lib/sandbox/control/task-workflow.js +1 -1
- package/dist/lib/sandbox/control/workflow-executor.js +90 -22
- package/dist/lib/server/process-state.js +17 -5
- package/dist/lib/task/artifact-command.js +6 -3
- package/dist/lib/task/artifact-lifecycle.js +15 -52
- package/dist/lib/task/artifact-operations.js +28 -2
- package/dist/lib/task/artifact-recovery.js +114 -68
- package/dist/lib/task/artifact-repair-intent.js +25 -8
- package/dist/lib/task/capabilities.js +0 -2
- package/dist/lib/task/control-authority.js +1 -1
- package/dist/lib/task/events.js +66 -49
- package/dist/lib/task/gate-policy.js +94 -0
- package/dist/lib/task/intermediate-cleanup.js +28 -3
- package/dist/lib/task/local-artifact-finalization.js +85 -18
- package/dist/lib/task/orchestration.js +8 -6
- package/dist/lib/task/review-artifacts.js +12 -6
- package/dist/lib/task/review-command.js +2 -2
- package/dist/lib/task/review-finalization.js +198 -11
- package/dist/lib/{host-control → task}/secure-fs.js +8 -19
- package/dist/lib/task/verification-engine.js +28 -17
- package/dist/lib/task/verification.js +25 -9
- package/dist/lib/task/workflow-command.js +2 -0
- package/dist/lib/task/workflow-dispatch.js +161 -0
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/internal/cli-route-inventory.ts +3 -4
- package/lib/internal/manual-validation.ts +6 -2
- package/lib/internal/platform-comment.ts +53 -2
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-finalization.ts +1 -1
- package/lib/internal/task-lifecycle.ts +1 -1
- package/lib/internal/task-operation-registry.ts +22 -12
- package/lib/internal/task-orchestration.ts +1 -1
- package/lib/internal/task-review.ts +9 -5
- package/lib/platform/activity-recovery.ts +179 -0
- package/lib/platform/issue-comments.ts +98 -31
- package/lib/platform/issues.ts +3 -4
- package/lib/platform/task-comment-projection.ts +79 -0
- package/lib/platform/task-recovery.ts +54 -0
- package/lib/platform/verification-sync.ts +203 -188
- package/lib/sandbox/control/executor.ts +4 -2
- package/lib/sandbox/control/protocol.ts +1 -1
- package/lib/sandbox/control/task-workflow.ts +1 -1
- package/lib/sandbox/control/workflow-executor.ts +113 -22
- package/lib/server/process-state.ts +26 -5
- package/lib/task/artifact-command.ts +6 -4
- package/lib/task/artifact-lifecycle.ts +12 -49
- package/lib/task/artifact-operations.ts +28 -2
- package/lib/task/artifact-recovery.ts +120 -67
- package/lib/task/artifact-repair-intent.ts +38 -11
- package/lib/task/capabilities.ts +0 -1
- package/lib/task/control-authority.ts +1 -1
- package/lib/task/events.ts +55 -38
- package/lib/task/gate-policy.ts +133 -0
- package/lib/task/intermediate-cleanup.ts +18 -2
- package/lib/task/local-artifact-finalization.ts +91 -15
- package/lib/task/orchestration.ts +12 -7
- package/lib/task/review-artifacts.ts +8 -2
- package/lib/task/review-command.ts +1 -1
- package/lib/task/review-finalization.ts +198 -9
- package/lib/{host-control → task}/secure-fs.ts +10 -32
- package/lib/task/verification-engine.ts +24 -14
- package/lib/task/verification-types.ts +7 -0
- package/lib/task/verification.ts +25 -9
- package/lib/task/workflow-command.ts +2 -0
- package/lib/task/workflow-dispatch.ts +171 -0
- package/package.json +1 -1
- package/templates/.agents/rules/issue-sync.en.md +11 -1
- package/templates/.agents/rules/issue-sync.zh-CN.md +6 -2
- package/templates/.agents/rules/local-artifact-repair.en.md +18 -9
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +21 -11
- package/templates/.agents/skills/analyze-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -10
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -10
- package/templates/.agents/skills/block-task/config/verify.json +1 -3
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -4
- package/templates/.agents/skills/code-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -12
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -12
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/report-template.en.md +11 -0
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +11 -0
- package/templates/.agents/skills/commit/config/verify.json +1 -5
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +6 -6
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +6 -6
- package/templates/.agents/skills/complete-task/config/verify.en.json +3 -14
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +3 -14
- package/templates/.agents/skills/create-pr/config/verify.json +1 -10
- package/templates/.agents/skills/create-task/config/verify.json +1 -4
- package/templates/.agents/skills/import-issue/config/verify.json +1 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -10
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -10
- package/templates/.agents/skills/restore-task/SKILL.en.md +9 -4
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +10 -5
- package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -10
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -10
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/review-code/SKILL.en.md +3 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +3 -2
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -10
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -10
- package/templates/.agents/skills/review-code/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -10
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -10
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/review-pr/config/verify.en.json +1 -8
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -8
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +38 -13
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +38 -13
- package/dist/lib/host-control/audit.js +0 -19
- package/dist/lib/host-control/client.js +0 -142
- package/dist/lib/host-control/command.js +0 -49
- package/dist/lib/host-control/path.js +0 -160
- package/dist/lib/host-control/server.js +0 -159
- package/dist/lib/host-control/service.js +0 -112
- package/dist/lib/internal/host-control.js +0 -28
- package/lib/host-control/audit.ts +0 -22
- package/lib/host-control/client.ts +0 -172
- package/lib/host-control/command.ts +0 -60
- package/lib/host-control/path.ts +0 -167
- package/lib/host-control/server.ts +0 -180
- package/lib/host-control/service.ts +0 -133
- package/lib/internal/host-control.ts +0 -28
|
@@ -83,7 +83,7 @@ Create `.agents/workspace/active/{task-id}/{review-artifact}`.
|
|
|
83
83
|
|
|
84
84
|
### 6. Update Task Status
|
|
85
85
|
|
|
86
|
-
After the report, submit each new finding with `agent-infra-internal task-ledger {task-id} finding-upsert --stage plan --review-artifact {review-artifact} --ordinal {n} --severity {blocker|major|minor} --evidence {review-artifact}#{anchor}
|
|
86
|
+
After the report, call `agent-infra-internal task-review {task-id} preflight --stage plan --artifact {review-artifact} {execution-flag}` before any ledger write. On failure, follow `.agents/rules/local-artifact-repair.md`; on success, bind `{recovery-id}` from the response. Only after preflight passes, submit each new finding with `agent-infra-internal task-ledger {task-id} finding-upsert --stage plan --review-artifact {review-artifact} --ordinal {n} --severity {blocker|major|minor} --evidence {review-artifact}#{anchor}` or prior-response dispositions with `finding-review --id {ledger-id} --status {confirmed|closed|open|needs-human-decision} --evidence {evidence}`. Do not scan ids or edit ledger rows. After all ledger writes, call `agent-infra-internal task-review {task-id} finalize-summary --stage plan --artifact {review-artifact} --recovery-id {recovery-id} {execution-flag}`; never treat an error type or `changed=false` as automatic authorization.
|
|
87
87
|
|
|
88
88
|
Bind and reuse this structured mapping from that one response:
|
|
89
89
|
|
|
@@ -86,7 +86,7 @@ agent-infra-internal task-artifact {task-id} init --family review-plan --artifac
|
|
|
86
86
|
|
|
87
87
|
### 6. 更新任务状态
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
报告完成后,先调用 `agent-infra-internal task-review {task-id} preflight --stage plan --artifact {review-artifact} {execution-flag}`,不得先写入账本。失败后是否继续编辑并重跑,必须遵循 `.agents/rules/local-artifact-repair.md`;通过时从返回值绑定 `{recovery-id}`。只有 preflight 通过后,才逐条调用 `agent-infra-internal task-ledger {task-id} finding-upsert --stage plan --review-artifact {review-artifact} --ordinal {n} --severity {blocker|major|minor} --evidence {review-artifact}#{anchor}`,或复核上一轮响应时调用 `finding-review --id {ledger-id} --status {confirmed|closed|open|needs-human-decision} --evidence {相称证据}`。不得扫描编号或手写账本行。全部账本写入完成后调用 `agent-infra-internal task-review {task-id} finalize-summary --stage plan --artifact {review-artifact} --recovery-id {recovery-id} {execution-flag}`;不能把失败类型或 `changed=false` 当作自动授权。
|
|
90
90
|
|
|
91
91
|
从该次返回值绑定并复用以下结构化映射:
|
|
92
92
|
|
|
@@ -27,16 +27,7 @@
|
|
|
27
27
|
"expected_action_pattern": "(Review Plan|Plan Review) \\(Round \\d+\\)"
|
|
28
28
|
},
|
|
29
29
|
"platform-sync": {
|
|
30
|
-
"when": "platform_issue_identity_exists"
|
|
31
|
-
"expected_status_label": "status: pending-design-work",
|
|
32
|
-
"expected_comment_marker": "<!-- sync-issue:{task-id}:{artifact-stem} -->",
|
|
33
|
-
"verify_comment_content": true,
|
|
34
|
-
"verify_task_comment_content": true,
|
|
35
|
-
"verify_issue_type": true,
|
|
36
|
-
"verify_issue_fields": false,
|
|
37
|
-
"verify_milestone": true,
|
|
38
|
-
"expected_status_label_key": "pendingDesignWork",
|
|
39
|
-
"expected_comment_marker_key": "artifact"
|
|
30
|
+
"when": "platform_issue_identity_exists"
|
|
40
31
|
}
|
|
41
32
|
}
|
|
42
33
|
}
|
|
@@ -27,16 +27,7 @@
|
|
|
27
27
|
"expected_action_pattern": "(Review Plan|Plan Review) \\(Round \\d+\\)"
|
|
28
28
|
},
|
|
29
29
|
"platform-sync": {
|
|
30
|
-
"when": "platform_issue_identity_exists"
|
|
31
|
-
"expected_status_label": "status: pending-design-work",
|
|
32
|
-
"expected_comment_marker": "<!-- sync-issue:{task-id}:{artifact-stem} -->",
|
|
33
|
-
"verify_comment_content": true,
|
|
34
|
-
"verify_task_comment_content": true,
|
|
35
|
-
"verify_issue_type": true,
|
|
36
|
-
"verify_issue_fields": false,
|
|
37
|
-
"verify_milestone": true,
|
|
38
|
-
"expected_status_label_key": "pendingDesignWork",
|
|
39
|
-
"expected_comment_marker_key": "artifact"
|
|
30
|
+
"when": "platform_issue_identity_exists"
|
|
40
31
|
}
|
|
41
32
|
}
|
|
42
33
|
}
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
Use this template when writing `review-plan.md` or `review-plan-r{N}.md`.
|
|
6
6
|
|
|
7
|
+
> The `Review Input` field provides reader-facing traceability. Lifecycle identity uses the input frozen by `review-plan.started` and the completion receipt; it is not parsed from this report body.
|
|
8
|
+
|
|
7
9
|
## Output Template
|
|
8
10
|
|
|
9
11
|
```markdown
|
|
@@ -24,14 +24,7 @@
|
|
|
24
24
|
"expected_action_pattern": "Review PR \\(Round \\d+\\)"
|
|
25
25
|
},
|
|
26
26
|
"platform-sync": {
|
|
27
|
-
"when": "platform_issue_identity_exists"
|
|
28
|
-
"expected_comment_marker": "<!-- sync-issue:{task-id}:{artifact-stem} -->",
|
|
29
|
-
"verify_comment_content": true,
|
|
30
|
-
"verify_task_comment_content": true,
|
|
31
|
-
"verify_issue_type": false,
|
|
32
|
-
"verify_issue_fields": false,
|
|
33
|
-
"verify_milestone": false,
|
|
34
|
-
"expected_comment_marker_key": "artifact"
|
|
27
|
+
"when": "platform_issue_identity_exists"
|
|
35
28
|
}
|
|
36
29
|
}
|
|
37
30
|
}
|
|
@@ -24,14 +24,7 @@
|
|
|
24
24
|
"expected_action_pattern": "Review PR \\(Round \\d+\\)"
|
|
25
25
|
},
|
|
26
26
|
"platform-sync": {
|
|
27
|
-
"when": "platform_issue_identity_exists"
|
|
28
|
-
"expected_comment_marker": "<!-- sync-issue:{task-id}:{artifact-stem} -->",
|
|
29
|
-
"verify_comment_content": true,
|
|
30
|
-
"verify_task_comment_content": true,
|
|
31
|
-
"verify_issue_type": false,
|
|
32
|
-
"verify_issue_fields": false,
|
|
33
|
-
"verify_milestone": false,
|
|
34
|
-
"expected_comment_marker_key": "artifact"
|
|
27
|
+
"when": "platform_issue_identity_exists"
|
|
35
28
|
}
|
|
36
29
|
}
|
|
37
30
|
}
|
|
@@ -2,24 +2,49 @@
|
|
|
2
2
|
"skill": "run-manual-validation",
|
|
3
3
|
"checks": {
|
|
4
4
|
"task-meta": {
|
|
5
|
-
"required_fields": [
|
|
5
|
+
"required_fields": [
|
|
6
|
+
"id",
|
|
7
|
+
"type",
|
|
8
|
+
"workflow",
|
|
9
|
+
"status",
|
|
10
|
+
"created_at",
|
|
11
|
+
"updated_at",
|
|
12
|
+
"agent_infra_version",
|
|
13
|
+
"current_step",
|
|
14
|
+
"assigned_to"
|
|
15
|
+
]
|
|
6
16
|
},
|
|
7
17
|
"artifact": {
|
|
8
18
|
"file_pattern": "validation-run.md|validation-run-r{N}.md",
|
|
9
|
-
"required_sections": [
|
|
10
|
-
|
|
19
|
+
"required_sections": [
|
|
20
|
+
"Input Mode",
|
|
21
|
+
"State Check",
|
|
22
|
+
"Validation Target",
|
|
23
|
+
"Discovered Items",
|
|
24
|
+
"Mode Decision",
|
|
25
|
+
"Command Summary",
|
|
26
|
+
"Structured Evidence",
|
|
27
|
+
"Per-item Results",
|
|
28
|
+
"Cleanup and Recovery",
|
|
29
|
+
"Remaining Coverage",
|
|
30
|
+
"Raw Evidence"
|
|
31
|
+
],
|
|
32
|
+
"required_patterns": [
|
|
33
|
+
"^\\$ "
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
"activity-log": {
|
|
37
|
+
"expected_action_pattern": "Run Manual Validation \\(Round \\d+\\)"
|
|
38
|
+
},
|
|
39
|
+
"review-ledger": {
|
|
40
|
+
"stage_scope": [
|
|
41
|
+
"analysis",
|
|
42
|
+
"plan",
|
|
43
|
+
"code"
|
|
44
|
+
]
|
|
11
45
|
},
|
|
12
|
-
"activity-log": { "expected_action_pattern": "Run Manual Validation \\(Round \\d+\\)" },
|
|
13
|
-
"review-ledger": { "stage_scope": ["analysis", "plan", "code"] },
|
|
14
46
|
"platform-sync": {
|
|
15
|
-
"when": "platform_issue_identity_exists"
|
|
16
|
-
"expected_comment_marker": "<!-- sync-issue:{task-id}:{artifact-stem} -->",
|
|
17
|
-
"verify_comment_content": true,
|
|
18
|
-
"verify_task_comment_content": true,
|
|
19
|
-
"verify_issue_type": true,
|
|
20
|
-
"verify_issue_fields": false,
|
|
21
|
-
"verify_milestone": true,
|
|
22
|
-
"expected_comment_marker_key": "artifact"
|
|
47
|
+
"when": "platform_issue_identity_exists"
|
|
23
48
|
}
|
|
24
49
|
}
|
|
25
50
|
}
|
|
@@ -2,24 +2,49 @@
|
|
|
2
2
|
"skill": "run-manual-validation",
|
|
3
3
|
"checks": {
|
|
4
4
|
"task-meta": {
|
|
5
|
-
"required_fields": [
|
|
5
|
+
"required_fields": [
|
|
6
|
+
"id",
|
|
7
|
+
"type",
|
|
8
|
+
"workflow",
|
|
9
|
+
"status",
|
|
10
|
+
"created_at",
|
|
11
|
+
"updated_at",
|
|
12
|
+
"agent_infra_version",
|
|
13
|
+
"current_step",
|
|
14
|
+
"assigned_to"
|
|
15
|
+
]
|
|
6
16
|
},
|
|
7
17
|
"artifact": {
|
|
8
18
|
"file_pattern": "validation-run.md|validation-run-r{N}.md",
|
|
9
|
-
"required_sections": [
|
|
10
|
-
|
|
19
|
+
"required_sections": [
|
|
20
|
+
"输入模式",
|
|
21
|
+
"状态核对",
|
|
22
|
+
"验证目标",
|
|
23
|
+
"发现清单",
|
|
24
|
+
"模式判定",
|
|
25
|
+
"命令摘要",
|
|
26
|
+
"结构化证据",
|
|
27
|
+
"逐项结果",
|
|
28
|
+
"清理与恢复",
|
|
29
|
+
"尚未覆盖",
|
|
30
|
+
"证据原文"
|
|
31
|
+
],
|
|
32
|
+
"required_patterns": [
|
|
33
|
+
"^\\$ "
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
"activity-log": {
|
|
37
|
+
"expected_action_pattern": "Run Manual Validation \\(Round \\d+\\)"
|
|
38
|
+
},
|
|
39
|
+
"review-ledger": {
|
|
40
|
+
"stage_scope": [
|
|
41
|
+
"analysis",
|
|
42
|
+
"plan",
|
|
43
|
+
"code"
|
|
44
|
+
]
|
|
11
45
|
},
|
|
12
|
-
"activity-log": { "expected_action_pattern": "Run Manual Validation \\(Round \\d+\\)" },
|
|
13
|
-
"review-ledger": { "stage_scope": ["analysis", "plan", "code"] },
|
|
14
46
|
"platform-sync": {
|
|
15
|
-
"when": "platform_issue_identity_exists"
|
|
16
|
-
"expected_comment_marker": "<!-- sync-issue:{task-id}:{artifact-stem} -->",
|
|
17
|
-
"verify_comment_content": true,
|
|
18
|
-
"verify_task_comment_content": true,
|
|
19
|
-
"verify_issue_type": true,
|
|
20
|
-
"verify_issue_fields": false,
|
|
21
|
-
"verify_milestone": true,
|
|
22
|
-
"expected_comment_marker_key": "artifact"
|
|
47
|
+
"when": "platform_issue_identity_exists"
|
|
23
48
|
}
|
|
24
49
|
}
|
|
25
50
|
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
export const HOST_CONTROL_AUDIT_FILE = 'audit.ndjson';
|
|
4
|
-
export function hostControlAuditPath(endpoint) {
|
|
5
|
-
return path.join(path.dirname(endpoint), HOST_CONTROL_AUDIT_FILE);
|
|
6
|
-
}
|
|
7
|
-
export function appendHostControlAudit(endpoint, entry, at = Date.now()) {
|
|
8
|
-
const filePath = hostControlAuditPath(endpoint);
|
|
9
|
-
const descriptor = fs.openSync(filePath, 'a', 0o600);
|
|
10
|
-
try {
|
|
11
|
-
fs.fchmodSync(descriptor, 0o600);
|
|
12
|
-
fs.writeFileSync(descriptor, `${JSON.stringify({ version: 1, at, ...entry })}\n`, 'utf8');
|
|
13
|
-
fs.fsyncSync(descriptor);
|
|
14
|
-
}
|
|
15
|
-
finally {
|
|
16
|
-
fs.closeSync(descriptor);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
//# sourceMappingURL=audit.js.map
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import net from 'node:net';
|
|
2
|
-
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import { inspectHostControlEndpoint, resolveHostControlEndpoint } from './path.js';
|
|
4
|
-
export const HOST_CONTROL_MAX_REQUEST_BYTES = 64 * 1024;
|
|
5
|
-
export const HOST_CONTROL_COMMANDS = Object.freeze([
|
|
6
|
-
'task-lifecycle', 'task-orchestration', 'task-finalization',
|
|
7
|
-
'task-artifact', 'task-review', 'task-event', 'task-ledger',
|
|
8
|
-
'task-invalidation', 'task-warning'
|
|
9
|
-
]);
|
|
10
|
-
export const HOST_CONTROL_TEST_ENVIRONMENT_KEYS = Object.freeze([
|
|
11
|
-
'TZ', 'AGENT_INFRA_GH_BIN', 'AGENT_INFRA_GH_ARGS_JSON', 'GH_FAKE_PR_PATH', 'GH_FAKE_ARGS_PATH'
|
|
12
|
-
]);
|
|
13
|
-
export class HostControlClientError extends Error {
|
|
14
|
-
code;
|
|
15
|
-
retryable;
|
|
16
|
-
changed;
|
|
17
|
-
constructor(code, message, retryable = false, submitted = false) {
|
|
18
|
-
super(`${code}: ${message}`);
|
|
19
|
-
this.name = 'HostControlClientError';
|
|
20
|
-
this.code = code;
|
|
21
|
-
this.retryable = retryable && !submitted;
|
|
22
|
-
this.changed = submitted ? null : false;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
function reject(message) {
|
|
26
|
-
throw new HostControlClientError('HOST_CONTROL_REQUEST_INVALID', message);
|
|
27
|
-
}
|
|
28
|
-
export function validateHostControlRequest(value) {
|
|
29
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
30
|
-
reject('request must be an object');
|
|
31
|
-
const request = value;
|
|
32
|
-
if (Object.keys(request).sort().join(',') !== 'generation,id,operation,payload,scope,taskId,version'
|
|
33
|
-
|| request.version !== 1 || typeof request.id !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(request.id)
|
|
34
|
-
|| (request.taskId !== null && (typeof request.taskId !== 'string' || !/^TASK-\d{8}-\d{6}$/u.test(request.taskId)))
|
|
35
|
-
|| (request.generation !== null && (typeof request.generation !== 'string' || !request.generation))
|
|
36
|
-
|| request.scope !== 'host-command' || !HOST_CONTROL_COMMANDS.includes(request.operation))
|
|
37
|
-
reject('request schema is invalid');
|
|
38
|
-
const payload = request.payload;
|
|
39
|
-
if (!payload || typeof payload !== 'object' || Array.isArray(payload)
|
|
40
|
-
|| !['args,environment,workingDirectory', 'args,workingDirectory'].includes(Object.keys(payload).sort().join(','))
|
|
41
|
-
|| !Array.isArray(payload.args) || payload.args.length > 128
|
|
42
|
-
|| !payload.args.every((arg) => typeof arg === 'string' && !/[\r\n]/u.test(arg))
|
|
43
|
-
|| typeof payload.workingDirectory !== 'string' || !payload.workingDirectory.startsWith('/'))
|
|
44
|
-
reject('host-command payload is invalid');
|
|
45
|
-
if (payload.environment !== undefined) {
|
|
46
|
-
if (!payload.environment || typeof payload.environment !== 'object' || Array.isArray(payload.environment))
|
|
47
|
-
reject('host-command environment is invalid');
|
|
48
|
-
for (const [key, value] of Object.entries(payload.environment)) {
|
|
49
|
-
if (!HOST_CONTROL_TEST_ENVIRONMENT_KEYS.includes(key)
|
|
50
|
-
|| typeof value !== 'string' || /[\r\n]/u.test(value))
|
|
51
|
-
reject('host-command environment is invalid');
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return request;
|
|
55
|
-
}
|
|
56
|
-
export function hostControlRequestForCommand(command, args, workingDirectory, environment = {}) {
|
|
57
|
-
if (!HOST_CONTROL_COMMANDS.includes(command))
|
|
58
|
-
reject('host-command is unsupported');
|
|
59
|
-
const taskArg = args[0];
|
|
60
|
-
const taskId = taskArg && /^TASK-\d{8}-\d{6}$/u.test(taskArg) ? taskArg : null;
|
|
61
|
-
return validateHostControlRequest({
|
|
62
|
-
version: 1,
|
|
63
|
-
id: randomUUID(),
|
|
64
|
-
taskId,
|
|
65
|
-
generation: null,
|
|
66
|
-
operation: command,
|
|
67
|
-
scope: 'host-command',
|
|
68
|
-
payload: {
|
|
69
|
-
workingDirectory,
|
|
70
|
-
args: [...args],
|
|
71
|
-
...(Object.keys(environment).length > 0 ? { environment } : {})
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
function parseResponse(value, id) {
|
|
76
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
77
|
-
throw new HostControlClientError('HOST_CONTROL_RESPONSE_INVALID', 'response must be an object');
|
|
78
|
-
const response = value;
|
|
79
|
-
if (Object.keys(response).sort().join(',') !== 'error,exitCode,id,status,stderr,stdout,version'
|
|
80
|
-
|| response.version !== 1 || response.id !== id
|
|
81
|
-
|| !['completed', 'rejected', 'unknown'].includes(response.status)
|
|
82
|
-
|| !Number.isSafeInteger(response.exitCode)
|
|
83
|
-
|| typeof response.stdout !== 'string' || typeof response.stderr !== 'string'
|
|
84
|
-
|| (response.error !== null && (!response.error || typeof response.error !== 'object'))) {
|
|
85
|
-
throw new HostControlClientError('HOST_CONTROL_RESPONSE_INVALID', 'response schema is invalid');
|
|
86
|
-
}
|
|
87
|
-
return response;
|
|
88
|
-
}
|
|
89
|
-
export async function requestHostControl(params) {
|
|
90
|
-
const request = validateHostControlRequest(params.request);
|
|
91
|
-
const endpoint = params.endpoint ?? resolveHostControlEndpoint();
|
|
92
|
-
const inspection = inspectHostControlEndpoint(endpoint, { uid: process.getuid?.() });
|
|
93
|
-
if (!inspection.ok)
|
|
94
|
-
throw new HostControlClientError(inspection.code ?? 'HOST_CONTROL_UNAVAILABLE', 'fixed host-control endpoint is unavailable', true);
|
|
95
|
-
const encoded = `${JSON.stringify(request)}\n`;
|
|
96
|
-
if (Buffer.byteLength(encoded, 'utf8') > HOST_CONTROL_MAX_REQUEST_BYTES)
|
|
97
|
-
throw new HostControlClientError('HOST_CONTROL_REQUEST_TOO_LARGE', 'request exceeds the control limit');
|
|
98
|
-
const timeoutMs = params.timeoutMs ?? 30_000;
|
|
99
|
-
return await new Promise((resolve, rejectPromise) => {
|
|
100
|
-
const socket = net.createConnection(endpoint);
|
|
101
|
-
let output = '';
|
|
102
|
-
let settled = false;
|
|
103
|
-
let submitted = false;
|
|
104
|
-
const failure = (code, message, retryable = false) => new HostControlClientError(code, message, retryable, submitted);
|
|
105
|
-
const finish = (error, response) => {
|
|
106
|
-
if (settled)
|
|
107
|
-
return;
|
|
108
|
-
settled = true;
|
|
109
|
-
clearTimeout(timer);
|
|
110
|
-
socket.destroy();
|
|
111
|
-
if (error)
|
|
112
|
-
rejectPromise(error);
|
|
113
|
-
else
|
|
114
|
-
resolve(response);
|
|
115
|
-
};
|
|
116
|
-
const timer = setTimeout(() => finish(failure('HOST_CONTROL_TIMEOUT', 'host-control did not respond; inspect task state before retrying', true)), timeoutMs);
|
|
117
|
-
socket.setEncoding('utf8');
|
|
118
|
-
socket.on('connect', () => { submitted = true; socket.end(encoded); });
|
|
119
|
-
socket.on('data', (chunk) => {
|
|
120
|
-
output += chunk;
|
|
121
|
-
if (Buffer.byteLength(output, 'utf8') > HOST_CONTROL_MAX_REQUEST_BYTES) {
|
|
122
|
-
finish(failure('HOST_CONTROL_RESPONSE_TOO_LARGE', 'host-control response exceeds the control limit'));
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
const newline = output.indexOf('\n');
|
|
126
|
-
if (newline < 0)
|
|
127
|
-
return;
|
|
128
|
-
try {
|
|
129
|
-
finish(undefined, parseResponse(JSON.parse(output.slice(0, newline)), request.id));
|
|
130
|
-
}
|
|
131
|
-
catch (error) {
|
|
132
|
-
finish(failure('HOST_CONTROL_RESPONSE_INVALID', String(error)));
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
socket.on('error', (error) => finish(failure('HOST_CONTROL_UNAVAILABLE', error.message, true)));
|
|
136
|
-
socket.on('close', () => {
|
|
137
|
-
if (!settled)
|
|
138
|
-
finish(failure('HOST_CONTROL_RESPONSE_INVALID', 'host-control closed without a response'));
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
//# sourceMappingURL=client.js.map
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { readHostControlWorkerToken, resolveHostControlEndpoint } from './path.js';
|
|
5
|
-
export async function dispatchHostControlCommand(request) {
|
|
6
|
-
const { payload } = request;
|
|
7
|
-
const compiledEntry = fileURLToPath(new URL('../../bin/internal-cli.js', import.meta.url));
|
|
8
|
-
const sourceEntry = fileURLToPath(new URL('../../bin/internal-cli.ts', import.meta.url));
|
|
9
|
-
const entry = fs.existsSync(compiledEntry) ? compiledEntry : sourceEntry;
|
|
10
|
-
const payloadEnvironment = payload.environment ?? {};
|
|
11
|
-
const env = { ...process.env, ...payloadEnvironment };
|
|
12
|
-
for (const key of Object.keys(env)) {
|
|
13
|
-
if (key.startsWith('AGENT_INFRA_CONTROL_')
|
|
14
|
-
|| key === 'AGENT_INFRA_TASK_ID'
|
|
15
|
-
|| key === 'AGENT_INFRA_RUNTIME_DIR'
|
|
16
|
-
|| key === 'AGENT_INFRA_EXECUTOR_MANIFEST'
|
|
17
|
-
|| key === 'AGENT_INFRA_HOST_CONTROL_SOCKET') {
|
|
18
|
-
delete env[key];
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
delete env.NODE_OPTIONS;
|
|
22
|
-
delete env.NODE_PATH;
|
|
23
|
-
delete env.AGENT_INFRA_HOST_CONTROL_WORKER;
|
|
24
|
-
delete env.AGENT_INFRA_HOST_CONTROL_WORKER_TOKEN;
|
|
25
|
-
env.AGENT_INFRA_HOST_CONTROL_WORKER = '1';
|
|
26
|
-
env.AGENT_INFRA_HOST_CONTROL_WORKER_TOKEN = readHostControlWorkerToken(process.env.AGENT_INFRA_TEST_HOST_CONTROL_ENDPOINT ?? resolveHostControlEndpoint());
|
|
27
|
-
return await new Promise((resolve, reject) => {
|
|
28
|
-
const runtimeArgs = entry.endsWith('.ts') ? ['--experimental-strip-types', '--no-warnings', entry] : [entry];
|
|
29
|
-
const child = spawn(process.execPath, [...runtimeArgs, request.operation, ...payload.args], {
|
|
30
|
-
cwd: payload.workingDirectory,
|
|
31
|
-
env,
|
|
32
|
-
stdio: ['ignore', 'pipe', 'pipe']
|
|
33
|
-
});
|
|
34
|
-
let stdout = '';
|
|
35
|
-
let stderr = '';
|
|
36
|
-
child.stdout.setEncoding('utf8');
|
|
37
|
-
child.stderr.setEncoding('utf8');
|
|
38
|
-
child.stdout.on('data', (chunk) => { stdout += chunk; });
|
|
39
|
-
child.stderr.on('data', (chunk) => { stderr += chunk; });
|
|
40
|
-
child.once('error', reject);
|
|
41
|
-
child.once('close', (code, signal) => {
|
|
42
|
-
if (signal)
|
|
43
|
-
reject(new Error(`HOST_CONTROL_WORKER_TERMINATED: ${signal}`));
|
|
44
|
-
else
|
|
45
|
-
resolve({ stdout, stderr, exitCode: code ?? 1 });
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
//# sourceMappingURL=command.js.map
|
|
@@ -1,160 +0,0 @@
|
|
|
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
|