holo-codex 0.1.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/.agents/plugins/marketplace.json +20 -0
- package/CONTRIBUTING.md +54 -0
- package/LICENSE +21 -0
- package/README.md +215 -0
- package/README.zh-CN.md +215 -0
- package/SECURITY.md +39 -0
- package/assets/brand/README.md +35 -0
- package/assets/brand/holo-codex-icon.svg +28 -0
- package/assets/brand/holo-codex-lockup.svg +49 -0
- package/assets/brand/holo-codex-mark.svg +33 -0
- package/assets/brand/holo-codex-plugin-card.png +0 -0
- package/assets/brand/holo-codex-plugin-card.svg +81 -0
- package/assets/brand/holo-codex-readme-hero.png +0 -0
- package/assets/brand/holo-codex-readme-hero.svg +140 -0
- package/assets/brand/holo-codex-social-preview.png +0 -0
- package/assets/brand/holo-codex-social-preview.svg +130 -0
- package/assets/brand/holo-codex-wordmark-options.svg +52 -0
- package/docs/checklists/agent-loop-first-delivery-audit.md +129 -0
- package/docs/examples/generic-loop-repo-hygiene.md +168 -0
- package/docs/install.md +190 -0
- package/docs/local-release-readiness.md +206 -0
- package/docs/release-checklist.md +144 -0
- package/docs/self-bootstrap.md +150 -0
- package/docs/trust-and-safety.md +45 -0
- package/package.json +83 -0
- package/plugins/autonomous-pr-loop/.codex-plugin/plugin.json +17 -0
- package/plugins/autonomous-pr-loop/.mcp.json +13 -0
- package/plugins/autonomous-pr-loop/bin/agent-loop.mjs +31 -0
- package/plugins/autonomous-pr-loop/core/artifacts.ts +164 -0
- package/plugins/autonomous-pr-loop/core/autonomy-policy.ts +206 -0
- package/plugins/autonomous-pr-loop/core/ci.ts +131 -0
- package/plugins/autonomous-pr-loop/core/cli-i18n.ts +123 -0
- package/plugins/autonomous-pr-loop/core/cli.ts +1413 -0
- package/plugins/autonomous-pr-loop/core/command-runner.ts +446 -0
- package/plugins/autonomous-pr-loop/core/command.ts +47 -0
- package/plugins/autonomous-pr-loop/core/config-editor.ts +140 -0
- package/plugins/autonomous-pr-loop/core/config.ts +293 -0
- package/plugins/autonomous-pr-loop/core/controller-host.ts +19 -0
- package/plugins/autonomous-pr-loop/core/dashboard-server.ts +536 -0
- package/plugins/autonomous-pr-loop/core/delivery-work-item.ts +217 -0
- package/plugins/autonomous-pr-loop/core/doctor.ts +335 -0
- package/plugins/autonomous-pr-loop/core/errors.ts +82 -0
- package/plugins/autonomous-pr-loop/core/gate-recovery.ts +176 -0
- package/plugins/autonomous-pr-loop/core/gates.ts +26 -0
- package/plugins/autonomous-pr-loop/core/generic-lifecycle.ts +399 -0
- package/plugins/autonomous-pr-loop/core/git.ts +213 -0
- package/plugins/autonomous-pr-loop/core/github.ts +269 -0
- package/plugins/autonomous-pr-loop/core/gitnexus.ts +90 -0
- package/plugins/autonomous-pr-loop/core/happy.ts +42 -0
- package/plugins/autonomous-pr-loop/core/hook-capture.ts +115 -0
- package/plugins/autonomous-pr-loop/core/hook-events.ts +22 -0
- package/plugins/autonomous-pr-loop/core/hook-installation.ts +85 -0
- package/plugins/autonomous-pr-loop/core/hook-observer.ts +84 -0
- package/plugins/autonomous-pr-loop/core/hook-policy.ts +423 -0
- package/plugins/autonomous-pr-loop/core/hook-router.ts +452 -0
- package/plugins/autonomous-pr-loop/core/index.ts +32 -0
- package/plugins/autonomous-pr-loop/core/local-install.ts +778 -0
- package/plugins/autonomous-pr-loop/core/locale.ts +60 -0
- package/plugins/autonomous-pr-loop/core/loop-shapes.ts +190 -0
- package/plugins/autonomous-pr-loop/core/mcp-controller.ts +1479 -0
- package/plugins/autonomous-pr-loop/core/notification-feed.ts +263 -0
- package/plugins/autonomous-pr-loop/core/plan-parser.ts +206 -0
- package/plugins/autonomous-pr-loop/core/plugin-paths.ts +32 -0
- package/plugins/autonomous-pr-loop/core/policy.ts +65 -0
- package/plugins/autonomous-pr-loop/core/pr-lifecycle.ts +464 -0
- package/plugins/autonomous-pr-loop/core/pr-selector.ts +284 -0
- package/plugins/autonomous-pr-loop/core/profiles.ts +439 -0
- package/plugins/autonomous-pr-loop/core/redaction.ts +17 -0
- package/plugins/autonomous-pr-loop/core/repo-root.ts +22 -0
- package/plugins/autonomous-pr-loop/core/review-comments.ts +77 -0
- package/plugins/autonomous-pr-loop/core/scope-guard.ts +179 -0
- package/plugins/autonomous-pr-loop/core/state-machine.ts +828 -0
- package/plugins/autonomous-pr-loop/core/state-types.ts +130 -0
- package/plugins/autonomous-pr-loop/core/storage.ts +2527 -0
- package/plugins/autonomous-pr-loop/core/types.ts +567 -0
- package/plugins/autonomous-pr-loop/core/worker-events.ts +412 -0
- package/plugins/autonomous-pr-loop/core/worker-policy.ts +72 -0
- package/plugins/autonomous-pr-loop/core/worker-prompts.ts +182 -0
- package/plugins/autonomous-pr-loop/core/worker.ts +809 -0
- package/plugins/autonomous-pr-loop/core/workflow-board.ts +1515 -0
- package/plugins/autonomous-pr-loop/hooks/dist/permission-request.js +2462 -0
- package/plugins/autonomous-pr-loop/hooks/dist/post-compact.js +2462 -0
- package/plugins/autonomous-pr-loop/hooks/dist/post-tool-use.js +2462 -0
- package/plugins/autonomous-pr-loop/hooks/dist/pre-compact.js +2462 -0
- package/plugins/autonomous-pr-loop/hooks/dist/pre-tool-use.js +3460 -0
- package/plugins/autonomous-pr-loop/hooks/dist/session-start.js +2462 -0
- package/plugins/autonomous-pr-loop/hooks/dist/stop.js +2462 -0
- package/plugins/autonomous-pr-loop/hooks/dist/user-prompt-submit.js +2462 -0
- package/plugins/autonomous-pr-loop/hooks/hooks.json +106 -0
- package/plugins/autonomous-pr-loop/hooks/observe-runner.ts +25 -0
- package/plugins/autonomous-pr-loop/hooks/permission-request.ts +4 -0
- package/plugins/autonomous-pr-loop/hooks/post-compact.ts +4 -0
- package/plugins/autonomous-pr-loop/hooks/post-tool-use.ts +4 -0
- package/plugins/autonomous-pr-loop/hooks/pre-compact.ts +4 -0
- package/plugins/autonomous-pr-loop/hooks/pre-tool-use.ts +44 -0
- package/plugins/autonomous-pr-loop/hooks/session-start.ts +4 -0
- package/plugins/autonomous-pr-loop/hooks/stop.ts +4 -0
- package/plugins/autonomous-pr-loop/hooks/user-prompt-submit.ts +4 -0
- package/plugins/autonomous-pr-loop/mcp-server/src/index.ts +87 -0
- package/plugins/autonomous-pr-loop/mcp-server/src/tools.ts +205 -0
- package/plugins/autonomous-pr-loop/package.json +9 -0
- package/plugins/autonomous-pr-loop/schemas/config.schema.json +74 -0
- package/plugins/autonomous-pr-loop/schemas/marketplace.schema.json +46 -0
- package/plugins/autonomous-pr-loop/schemas/plugin.schema.json +32 -0
- package/plugins/autonomous-pr-loop/schemas/state.schema.json +19 -0
- package/plugins/autonomous-pr-loop/schemas/worker-event.schema.json +19 -0
- package/plugins/autonomous-pr-loop/schemas/worker-result.schema.json +58 -0
- package/plugins/autonomous-pr-loop/scripts/agent-loop.ts +44 -0
- package/plugins/autonomous-pr-loop/skills/autonomous-pr-loop/SKILL.md +26 -0
- package/plugins/autonomous-pr-loop/skills/autonomous-pr-loop/agents/openai.yaml +6 -0
- package/plugins/autonomous-pr-loop/ui/index.html +26 -0
- package/plugins/autonomous-pr-loop/ui/public/favicon.svg +7 -0
- package/plugins/autonomous-pr-loop/ui/src/api.ts +639 -0
- package/plugins/autonomous-pr-loop/ui/src/app.tsx +238 -0
- package/plugins/autonomous-pr-loop/ui/src/components/ActivityBadge.tsx +31 -0
- package/plugins/autonomous-pr-loop/ui/src/components/BrandMark.tsx +36 -0
- package/plugins/autonomous-pr-loop/ui/src/components/Collapsible.tsx +6 -0
- package/plugins/autonomous-pr-loop/ui/src/components/CommandPreview.tsx +15 -0
- package/plugins/autonomous-pr-loop/ui/src/components/ConfigEditor.tsx +389 -0
- package/plugins/autonomous-pr-loop/ui/src/components/EmptyState.tsx +10 -0
- package/plugins/autonomous-pr-loop/ui/src/components/ErrorState.tsx +12 -0
- package/plugins/autonomous-pr-loop/ui/src/components/List.tsx +7 -0
- package/plugins/autonomous-pr-loop/ui/src/components/MetricRow.tsx +6 -0
- package/plugins/autonomous-pr-loop/ui/src/components/ResponsiveTable.tsx +65 -0
- package/plugins/autonomous-pr-loop/ui/src/components/RiskBadge.tsx +10 -0
- package/plugins/autonomous-pr-loop/ui/src/components/StatusBadge.tsx +29 -0
- package/plugins/autonomous-pr-loop/ui/src/components/TopMetric.tsx +10 -0
- package/plugins/autonomous-pr-loop/ui/src/fixtures.ts +1152 -0
- package/plugins/autonomous-pr-loop/ui/src/i18n.ts +1105 -0
- package/plugins/autonomous-pr-loop/ui/src/main.tsx +14 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/CommandCenter.tsx +470 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/CommandCenterParts.tsx +276 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/agent-timeline/AgentTimelineView.tsx +73 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/artifact-viewer/ArtifactViewer.tsx +44 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/dry-run-preview/DryRunPreview.tsx +66 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/event-ledger/EventLedger.tsx +17 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/gate-center/GateCenter.tsx +34 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/mission-control/MissionControl.tsx +104 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/mission-control/WorkflowBoard.tsx +577 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/notifications/NotificationsView.tsx +30 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/plan-navigator/PlanNavigator.tsx +19 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/policy-config/PolicyConfig.tsx +22 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/pr-inbox/PrInbox.tsx +26 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/recovery-center/RecoveryCenter.tsx +125 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/scope-guard/ScopeGuard.tsx +16 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/worker-runs/WorkerRuns.tsx +39 -0
- package/plugins/autonomous-pr-loop/ui/src/styles.css +2673 -0
- package/plugins/autonomous-pr-loop/ui/src/theme.ts +57 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# HOLO-Codex npm Release Checklist
|
|
2
|
+
|
|
3
|
+
Use this checklist for npm releases such as `v0.1.0`.
|
|
4
|
+
|
|
5
|
+
The public source release remains available at `https://github.com/tizerluo/HOLO-Codex`. The npm package is `holo-codex` and installs the stable `agent-loop` CLI. Compatibility identifiers remain unchanged: `agent-loop`, `.agent-loop/`, `autonomous-pr-loop`, and `plugins/autonomous-pr-loop/`.
|
|
6
|
+
|
|
7
|
+
## Pre-Publish Validation
|
|
8
|
+
|
|
9
|
+
Run from the release checkout:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm install --frozen-lockfile
|
|
13
|
+
pnpm build:hooks
|
|
14
|
+
pnpm lint
|
|
15
|
+
pnpm test
|
|
16
|
+
npm pack --ignore-scripts --dry-run --json
|
|
17
|
+
npm view holo-codex name version dist-tags --json || true
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Before the first publish, `npm view holo-codex ...` should return `E404`. For later releases, confirm the registry version is lower than the version in `package.json`.
|
|
21
|
+
|
|
22
|
+
Review the pack output for accidental private material. The package must include the CLI, hooks dist, dashboard UI source, schemas, plugin metadata, MCP server, skills, public docs, and brand assets. It must not include tests, private planning/spec/research docs, `.agent-loop/`, raw logs, raw hook payloads, raw transcripts, or local backups.
|
|
23
|
+
|
|
24
|
+
Run a secret and local-state scan:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
rg -n "(ghp_|gho_|github_pat_|sk-[A-Za-z0-9]|dashboard token|AGENT_LOOP_MCP_TOKEN)" .
|
|
28
|
+
find . -path './.git' -prune -o -path './node_modules' -prune -o -name '.agent-loop' -print
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Expected result: no real tokens, no committed `.agent-loop/`, no raw hook payloads, no raw transcripts, no private handoff, and no historical `docs/plans/`, `docs/specs/`, or `docs/research/` material.
|
|
32
|
+
|
|
33
|
+
## Tarball Smoke
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm pack --ignore-scripts --json
|
|
37
|
+
tmp="$(mktemp -d)"
|
|
38
|
+
export CODEX_HOME="$tmp/codex-home"
|
|
39
|
+
mkdir -p "$tmp/target-repo"
|
|
40
|
+
git -C "$tmp/target-repo" init -b main
|
|
41
|
+
git -C "$tmp/target-repo" remote add origin https://github.com/example/holo-codex-smoke.git
|
|
42
|
+
npm install --prefix "$tmp/install" ./holo-codex-*.tgz
|
|
43
|
+
"$tmp/install/node_modules/.bin/agent-loop" --help
|
|
44
|
+
"$tmp/install/node_modules/.bin/agent-loop" --repo "$tmp/target-repo" init --json
|
|
45
|
+
"$tmp/install/node_modules/.bin/agent-loop" install-hooks --repo "$tmp/target-repo" --json
|
|
46
|
+
"$tmp/install/node_modules/.bin/agent-loop" --repo "$tmp/target-repo" local doctor --json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Do not publish if this smoke fails.
|
|
50
|
+
|
|
51
|
+
## Publish
|
|
52
|
+
|
|
53
|
+
Confirm npm authentication without printing tokens:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm whoami
|
|
57
|
+
npm ping --json
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Publish:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm publish --access public
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
If npm requires 2FA, complete the interactive prompt locally. Do not paste npm tokens or one-time codes into docs, PR bodies, issue comments, commits, artifacts, or screenshots.
|
|
67
|
+
|
|
68
|
+
## Post-Publish Smoke
|
|
69
|
+
|
|
70
|
+
Install from the registry in a fresh temporary prefix:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
tmp="$(mktemp -d)"
|
|
74
|
+
export CODEX_HOME="$tmp/codex-home"
|
|
75
|
+
mkdir -p "$tmp/target-repo"
|
|
76
|
+
git -C "$tmp/target-repo" init -b main
|
|
77
|
+
git -C "$tmp/target-repo" remote add origin https://github.com/example/holo-codex-smoke.git
|
|
78
|
+
npm install --prefix "$tmp/install" holo-codex
|
|
79
|
+
"$tmp/install/node_modules/.bin/agent-loop" --help
|
|
80
|
+
"$tmp/install/node_modules/.bin/agent-loop" local doctor --help
|
|
81
|
+
"$tmp/install/node_modules/.bin/agent-loop" --repo "$tmp/target-repo" init --json
|
|
82
|
+
"$tmp/install/node_modules/.bin/agent-loop" install-hooks --repo "$tmp/target-repo" --json
|
|
83
|
+
test -f "$tmp/install/node_modules/holo-codex/plugins/autonomous-pr-loop/.codex-plugin/plugin.json"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Optional dashboard smoke:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
"$tmp/install/node_modules/.bin/agent-loop" --repo "$tmp/target-repo" dashboard
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Open the printed loopback URL and confirm Mission Control loads without a token in the URL. Do not copy the fallback token into release notes, screenshots, logs, or PR comments.
|
|
93
|
+
|
|
94
|
+
## Source Install Smoke
|
|
95
|
+
|
|
96
|
+
Keep the source path working as a fallback and development path:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
git clone https://github.com/tizerluo/HOLO-Codex.git /tmp/holo-codex-release-smoke
|
|
100
|
+
cd /tmp/holo-codex-release-smoke
|
|
101
|
+
pnpm install --frozen-lockfile
|
|
102
|
+
pnpm lint
|
|
103
|
+
pnpm test
|
|
104
|
+
pnpm build:hooks
|
|
105
|
+
pnpm agent-loop local install --repo /path/to/sandbox-repo
|
|
106
|
+
agent-loop local doctor --repo /path/to/sandbox-repo
|
|
107
|
+
agent-loop --repo /path/to/sandbox-repo doctor
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Rollback Smoke
|
|
111
|
+
|
|
112
|
+
Use a fake or disposable `CODEX_HOME` unless a real rollback is explicitly intended:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
export CODEX_HOME=/tmp/holo-codex-release-codex-home
|
|
116
|
+
agent-loop local install --repo /path/to/sandbox-repo
|
|
117
|
+
agent-loop local snapshots
|
|
118
|
+
agent-loop local rollback --snapshot /path/to/snapshot
|
|
119
|
+
agent-loop local doctor --repo /path/to/sandbox-repo
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Expected result: hooks and binding registry restore from the snapshot, non-agent-loop hooks remain untouched, and malformed current hook files are preserved with a `.broken-<timestamp>` suffix when present.
|
|
123
|
+
|
|
124
|
+
## GitHub Release
|
|
125
|
+
|
|
126
|
+
After publish and post-publish smoke pass:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
git tag v0.1.0
|
|
130
|
+
git push origin v0.1.0
|
|
131
|
+
gh release create v0.1.0 \
|
|
132
|
+
--repo tizerluo/HOLO-Codex \
|
|
133
|
+
--title "HOLO-Codex v0.1.0" \
|
|
134
|
+
--notes-file /path/to/release-notes.md
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Release notes must state:
|
|
138
|
+
|
|
139
|
+
- HOLO-Codex is an observable workflow loop control plane for long-running Codex work; PR delivery is the first bundled workflow.
|
|
140
|
+
- npm install path: `npm install --global holo-codex`.
|
|
141
|
+
- Source install path: `git clone https://github.com/tizerluo/HOLO-Codex.git`.
|
|
142
|
+
- Rollback uses `agent-loop local rollback --snapshot <snapshot>`.
|
|
143
|
+
- npm uninstall removes the package, but shared HOLO-Codex router entries in `~/.codex/hooks.json` must be removed manually only after all target repo bindings are gone.
|
|
144
|
+
- Runtime state, tokens, raw hook payloads, raw transcripts, and dashboard tokens must not be committed or shared.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# HOLO-Codex Self-Bootstrapping Workflow
|
|
2
|
+
|
|
3
|
+
这份 runbook 说明 HOLO-Codex 如何使用自己的 `agent-loop` 交付流程维护自己。
|
|
4
|
+
|
|
5
|
+
默认下一项工作来源是 **GitHub issues**。历史 plans/specs/research 不属于公开源码树;只有 issue 明确要求新增公开设计文档时,才新增新的公开文档。
|
|
6
|
+
|
|
7
|
+
## 启动一次自举维护
|
|
8
|
+
|
|
9
|
+
先从干净且最新的 `main` 开始:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
git status --short --branch
|
|
13
|
+
git switch main
|
|
14
|
+
git pull --ff-only origin main
|
|
15
|
+
pnpm agent-loop status --json
|
|
16
|
+
pnpm agent-loop observe --json
|
|
17
|
+
gh issue list --repo tizerluo/HOLO-Codex --state open --limit 30
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
如果目标是审计一次 dashboard-first 或 agent-loop-first 交付,使用 [Agent-loop-first Delivery Audit Checklist](./checklists/agent-loop-first-delivery-audit.md) 记录检查动作、证据位置和 PASS/PARTIAL/GAP 结论。
|
|
21
|
+
|
|
22
|
+
如果 status 是 `BLOCKED`,不要直接开新实现分支。先通过 `status`、`observe`、`timeline`、`workers` 检查 gate:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pnpm agent-loop timeline --limit 20 --json
|
|
26
|
+
pnpm agent-loop workers --events --json
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
如果 active terminal worker gate 已经过时,可以显式 recovery,再决定是否 resume:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pnpm agent-loop recover --json
|
|
33
|
+
pnpm agent-loop resume
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
如果 gate 仍然有效,应该 stop run,或带 note approve gate。不要只在聊天里绕过 active gate;每个决定都必须留下 CLI、dashboard、event 或 PR 记录。
|
|
37
|
+
|
|
38
|
+
## 选择下一项工作
|
|
39
|
+
|
|
40
|
+
选择顺序:
|
|
41
|
+
|
|
42
|
+
1. 用户明确指定的 issue 优先。
|
|
43
|
+
2. 否则从 open GitHub issues 中,结合当前 handoff 优先级选择。
|
|
44
|
+
3. manual goal 只用于很小的人工 override;如果目标不小,先创建或选择 GitHub issue。
|
|
45
|
+
4. 公开源码树不依赖历史 `docs/specs` 或 `docs/plans` 队列;需要设计背景时,以当前 issue 和公开 docs 为准。
|
|
46
|
+
|
|
47
|
+
开工前读取 issue body、当前 handoff 和相关文档。保持一个 issue 一个 PR。相关且安全的小发现应尽量在当前 PR 修掉,不要制造不必要的 follow-up issue。
|
|
48
|
+
|
|
49
|
+
真实 `$pr-delivery-loop` 工作应先绑定 dashboard-visible run:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pnpm agent-loop init
|
|
53
|
+
pnpm agent-loop install-hooks --repo "$PWD" --json
|
|
54
|
+
pnpm agent-loop delivery bind \
|
|
55
|
+
--issue ISSUE_NUMBER \
|
|
56
|
+
--title "Issue title" \
|
|
57
|
+
--url https://github.com/tizerluo/HOLO-Codex/issues/ISSUE_NUMBER \
|
|
58
|
+
--json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Fresh repo 必须先 `init`,否则 `delivery bind` 没有 `.agent-loop/config.json` 和本地 SQLite 状态可写。已有 `.agent-loop/` 的仓库可以跳过重复初始化。
|
|
62
|
+
|
|
63
|
+
后续手工 commander 动作、review report、CI/merge readiness 和 cleanup 应记录到同一个 run。阶段开始和完成优先使用 `pnpm agent-loop delivery stage ...`,让 Mission Control 在文件编辑前就能显示当前阶段:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pnpm agent-loop delivery stage \
|
|
67
|
+
--run RUN_ID \
|
|
68
|
+
--stage build \
|
|
69
|
+
--substage implementation_active \
|
|
70
|
+
--status active \
|
|
71
|
+
--summary "Implementation started after plan approval." \
|
|
72
|
+
--json
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
普通证据、review report、CI/merge readiness 和 cleanup 仍可用 `pnpm agent-loop evidence append ...`。PR body、PR owner comment、tester/reviewer/Claude/AGY report comment 都应包含 run id,方便 GitHub 和 dashboard 互相对照。
|
|
76
|
+
|
|
77
|
+
Review/tester report 不只写自由文本 summary。派发、启动、完成、跳过或失败都应写结构化 review evidence:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pnpm agent-loop evidence append \
|
|
81
|
+
--run RUN_ID \
|
|
82
|
+
--stage review \
|
|
83
|
+
--substage claude_acp_review \
|
|
84
|
+
--reviewer claude_acp \
|
|
85
|
+
--requirement required \
|
|
86
|
+
--progress complete \
|
|
87
|
+
--result pass \
|
|
88
|
+
--severity none \
|
|
89
|
+
--comment-url "https://github.com/OWNER/REPO/pull/PR#issuecomment-ID" \
|
|
90
|
+
--summary "Claude ACP review completed with PASS." \
|
|
91
|
+
--json
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`complete` review evidence 必须链接 PR issue comment;`block` 或 `p2_or_higher` 会让 merge readiness 保持阻塞,直到同 PR 修复或按规则路由。
|
|
95
|
+
|
|
96
|
+
## 交付一个 PR
|
|
97
|
+
|
|
98
|
+
每个 issue 使用这条 loop:
|
|
99
|
+
|
|
100
|
+
```text
|
|
101
|
+
read issue and handoff
|
|
102
|
+
-> run GitNexus impact for code symbols to be edited
|
|
103
|
+
-> write or update the development plan
|
|
104
|
+
-> create branch from main
|
|
105
|
+
-> implement
|
|
106
|
+
-> run focused tests
|
|
107
|
+
-> run independent tester/reviewer when useful or requested
|
|
108
|
+
-> fix all real P0/P1/P2 findings
|
|
109
|
+
-> run pnpm lint, pnpm test, and GitNexus detect
|
|
110
|
+
-> commit, push, open PR
|
|
111
|
+
-> wait for CI/review
|
|
112
|
+
-> fix any CI/review blocker in the same PR
|
|
113
|
+
-> merge
|
|
114
|
+
-> switch main, pull, rebuild GitNexus index
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
合并后的固定收尾:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
git switch main
|
|
121
|
+
git pull --ff-only origin main
|
|
122
|
+
npx gitnexus analyze
|
|
123
|
+
git status --short --branch
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Review 和 follow-up 纪律
|
|
127
|
+
|
|
128
|
+
- P0/P1 必须在当前 PR 修。
|
|
129
|
+
- P2 如果相关且安全,应在当前 PR 修。
|
|
130
|
+
- 只有当问题真实、超出当前 issue、且不能安全地在当前 PR 完成时,才创建 follow-up issue。
|
|
131
|
+
- P3 polish 如果很小,通常直接在当前 PR 修;否则只记录,不制造 issue 噪音。
|
|
132
|
+
- CI failure 永远不是当前 PR 的非阻塞 follow-up。
|
|
133
|
+
|
|
134
|
+
## CLI 安全注意
|
|
135
|
+
|
|
136
|
+
Mutating commands 包括 `recover`、`approve-gate`、`resume`、`stop`、`run`、`step`、`install-hooks`、`hooks install-router`、`hooks bind`、`hooks unbind`。`--help` 必须只打印 usage,不能修改 `.agent-loop` 状态或 hook registry。
|
|
137
|
+
|
|
138
|
+
Dashboard URL 只绑定 loopback。token 是本地 session secret,不能写入 commit、PR body、docs、logs、artifacts 或截图。
|
|
139
|
+
|
|
140
|
+
## 修改本 runbook 时的验证
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
pnpm test plugins/autonomous-pr-loop/tests/cli-run.test.ts
|
|
144
|
+
pnpm lint
|
|
145
|
+
pnpm test
|
|
146
|
+
pnpm agent-loop status --json
|
|
147
|
+
pnpm agent-loop observe --json
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
只有 PR 改 dashboard UI 或 live dashboard 行为时,才需要 Browser 验收。
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Trust And Safety
|
|
2
|
+
|
|
3
|
+
English: Safety boundaries keep workers scoped, supervisor actions auditable, and secrets out of durable artifacts.
|
|
4
|
+
|
|
5
|
+
中文:安全边界确保 worker 只做受控实现,supervisor 操作可审计,密钥不会进入持久化 artifacts。
|
|
6
|
+
|
|
7
|
+
See also: [README](../README.md) / [中文 README](../README.zh-CN.md).
|
|
8
|
+
|
|
9
|
+
## Worker Boundary
|
|
10
|
+
|
|
11
|
+
Delegated workers may edit workspace files and return structured results. They must not commit, push, create PRs, mark PRs ready, or merge. The supervisor owns Git and GitHub lifecycle actions.
|
|
12
|
+
|
|
13
|
+
Worker prompts, command policy, Codex sandboxing, and PR E hooks all reinforce this boundary.
|
|
14
|
+
|
|
15
|
+
## Hooks Coverage
|
|
16
|
+
|
|
17
|
+
Hooks cover the Codex tool loop only. `PreToolUse` blocks destructive Git/GitHub commands and lifecycle actions when state gates are not satisfied.
|
|
18
|
+
|
|
19
|
+
Hooks do not intercept commands a user runs in an external Terminal.
|
|
20
|
+
|
|
21
|
+
## MCP Mutations
|
|
22
|
+
|
|
23
|
+
Mutating MCP tools require `AGENT_LOOP_MCP_TOKEN`. Calls without the matching token return `needs_secret_or_login` and do not update loop state.
|
|
24
|
+
|
|
25
|
+
## Merge Safety
|
|
26
|
+
|
|
27
|
+
`mergeMode` is the canonical merge policy. Legacy `allowAutoMerge` is accepted only as compatibility input. Merge still requires review, CI, open-comment, scope, and policy gates to pass.
|
|
28
|
+
|
|
29
|
+
## Generic Loop Safety
|
|
30
|
+
|
|
31
|
+
`generic-loop` uses the same supervisor, storage, gate, artifact, and audit boundaries as `pr-loop`. Planning and review states run read-only. Write-capable generic states require profile-scoped write roots and still pass through scope guard and hook policy.
|
|
32
|
+
|
|
33
|
+
## Dashboard Tokens
|
|
34
|
+
|
|
35
|
+
`agent-loop dashboard` prints the loopback URL on stdout and the session token on stderr. The dashboard login stores the token in browser localStorage and sends it as `x-agent-loop-token`. Mutation endpoints still require the token and loopback/origin guard. CLI `observe --json`, audit export, and normal dashboard URLs do not include the token.
|
|
36
|
+
|
|
37
|
+
## Secrets And Logs
|
|
38
|
+
|
|
39
|
+
Do not write secrets to code, docs, reports, logs, artifacts, commits, or PR bodies. Store local credentials in the operating system keychain or the user's configured secret manager.
|
|
40
|
+
|
|
41
|
+
Command output may be stored as `.agent-loop/` artifacts. Review output before sharing it outside the local machine.
|
|
42
|
+
|
|
43
|
+
## Runtime State
|
|
44
|
+
|
|
45
|
+
`.agent-loop/`, SQLite state files, WAL/SHM files, raw worker JSONL, and hook logs are runtime data and must not be committed.
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "holo-codex",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Human On Loop Codex control plane for observable, recoverable Codex workflow loops.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "tizerluo",
|
|
7
|
+
"homepage": "https://github.com/tizerluo/HOLO-Codex#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/tizerluo/HOLO-Codex.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/tizerluo/HOLO-Codex/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"codex",
|
|
17
|
+
"codex-plugin",
|
|
18
|
+
"agent",
|
|
19
|
+
"workflow",
|
|
20
|
+
"human-on-loop",
|
|
21
|
+
"observability",
|
|
22
|
+
"automation"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"bin": {
|
|
26
|
+
"agent-loop": "plugins/autonomous-pr-loop/bin/agent-loop.mjs"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"README.md",
|
|
30
|
+
"README.zh-CN.md",
|
|
31
|
+
"LICENSE",
|
|
32
|
+
"SECURITY.md",
|
|
33
|
+
"CONTRIBUTING.md",
|
|
34
|
+
"assets/brand/",
|
|
35
|
+
"docs/checklists/",
|
|
36
|
+
"docs/examples/",
|
|
37
|
+
"docs/install.md",
|
|
38
|
+
"docs/local-release-readiness.md",
|
|
39
|
+
"docs/release-checklist.md",
|
|
40
|
+
"docs/self-bootstrap.md",
|
|
41
|
+
"docs/trust-and-safety.md",
|
|
42
|
+
".agents/plugins/marketplace.json",
|
|
43
|
+
"plugins/autonomous-pr-loop/.codex-plugin/",
|
|
44
|
+
"plugins/autonomous-pr-loop/.mcp.json",
|
|
45
|
+
"plugins/autonomous-pr-loop/bin/",
|
|
46
|
+
"plugins/autonomous-pr-loop/core/",
|
|
47
|
+
"plugins/autonomous-pr-loop/hooks/",
|
|
48
|
+
"plugins/autonomous-pr-loop/mcp-server/",
|
|
49
|
+
"plugins/autonomous-pr-loop/package.json",
|
|
50
|
+
"plugins/autonomous-pr-loop/schemas/",
|
|
51
|
+
"plugins/autonomous-pr-loop/scripts/",
|
|
52
|
+
"plugins/autonomous-pr-loop/skills/",
|
|
53
|
+
"plugins/autonomous-pr-loop/ui/",
|
|
54
|
+
"tsconfig.json"
|
|
55
|
+
],
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=22.5"
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"agent-loop": "tsx plugins/autonomous-pr-loop/scripts/agent-loop.ts",
|
|
61
|
+
"build:hooks": "esbuild plugins/autonomous-pr-loop/hooks/pre-tool-use.ts plugins/autonomous-pr-loop/hooks/post-tool-use.ts plugins/autonomous-pr-loop/hooks/user-prompt-submit.ts plugins/autonomous-pr-loop/hooks/stop.ts plugins/autonomous-pr-loop/hooks/session-start.ts plugins/autonomous-pr-loop/hooks/pre-compact.ts plugins/autonomous-pr-loop/hooks/post-compact.ts plugins/autonomous-pr-loop/hooks/permission-request.ts --bundle --platform=node --format=esm --outdir=plugins/autonomous-pr-loop/hooks/dist",
|
|
62
|
+
"prepack": "pnpm build:hooks",
|
|
63
|
+
"test": "vitest run",
|
|
64
|
+
"lint": "tsc --noEmit"
|
|
65
|
+
},
|
|
66
|
+
"dependencies": {
|
|
67
|
+
"lucide-react": "^1.17.0",
|
|
68
|
+
"react": "^19.2.7",
|
|
69
|
+
"react-dom": "^19.2.7",
|
|
70
|
+
"tsx": "^4.21.0",
|
|
71
|
+
"vite": "^8.0.16"
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@testing-library/react": "^16.3.2",
|
|
75
|
+
"@types/node": "^25.0.1",
|
|
76
|
+
"@types/react": "^19.2.17",
|
|
77
|
+
"@types/react-dom": "^19.2.3",
|
|
78
|
+
"esbuild": "^0.28.1",
|
|
79
|
+
"jsdom": "^29.1.1",
|
|
80
|
+
"typescript": "^5.9.3",
|
|
81
|
+
"vitest": "^4.0.15"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "autonomous-pr-loop",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Human On Loop Codex control plane for observable, recoverable Codex workflow loops.",
|
|
5
|
+
"skills": "./skills/",
|
|
6
|
+
"interface": {
|
|
7
|
+
"displayName": "HOLO-Codex",
|
|
8
|
+
"shortDescription": "Turn long-running Codex workflows into observable Human On Loop systems.",
|
|
9
|
+
"longDescription": "A portable Codex plugin for durable workflow state, gates, evidence, hooks, worker orchestration, and the first bundled PR delivery workflow.",
|
|
10
|
+
"developerName": "tizerluo",
|
|
11
|
+
"category": "Engineering",
|
|
12
|
+
"capabilities": ["Write"],
|
|
13
|
+
"defaultPrompt": ["进入 HOLO-Codex,继续跑到 gate"],
|
|
14
|
+
"screenshots": ["../../../assets/brand/holo-codex-plugin-card.png"],
|
|
15
|
+
"brandColor": "#2563EB"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { dirname, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../../..");
|
|
8
|
+
const script = resolve(packageRoot, "plugins/autonomous-pr-loop/scripts/agent-loop.ts");
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
const tsxLoader = require.resolve("tsx");
|
|
11
|
+
const child = spawn(process.execPath, ["--import", tsxLoader, script, ...process.argv.slice(2)], {
|
|
12
|
+
cwd: process.cwd(),
|
|
13
|
+
env: process.env,
|
|
14
|
+
stdio: "inherit"
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
process.on("SIGINT", () => {});
|
|
18
|
+
process.on("SIGTERM", () => {});
|
|
19
|
+
|
|
20
|
+
child.on("exit", (code, signal) => {
|
|
21
|
+
if (signal) {
|
|
22
|
+
process.kill(process.pid, signal);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
process.exit(code ?? 1);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
child.on("error", (error) => {
|
|
29
|
+
process.stderr.write(`agent-loop: failed to start CLI runner: ${error.message}\n`);
|
|
30
|
+
process.exitCode = 1;
|
|
31
|
+
});
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { AgentLoopError } from "./errors.js";
|
|
5
|
+
import { ARTIFACT_KINDS, type ArtifactKind, type ArtifactRecord } from "./state-types.js";
|
|
6
|
+
import type { AgentLoopStorage } from "./types.js";
|
|
7
|
+
|
|
8
|
+
export interface ArtifactWriter {
|
|
9
|
+
id: string;
|
|
10
|
+
path: string;
|
|
11
|
+
append(content: string | Buffer): void;
|
|
12
|
+
finalize(): ArtifactRecord;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Write a run artifact to disk, persist metadata, and return its record. */
|
|
16
|
+
export function writeArtifact(
|
|
17
|
+
repoRoot: string,
|
|
18
|
+
storage: AgentLoopStorage,
|
|
19
|
+
runId: string,
|
|
20
|
+
kind: ArtifactKind,
|
|
21
|
+
name: string,
|
|
22
|
+
content: string | Buffer
|
|
23
|
+
): ArtifactRecord {
|
|
24
|
+
assertArtifactKind(kind);
|
|
25
|
+
const id = randomUUID();
|
|
26
|
+
const safeName = sanitizeName(name);
|
|
27
|
+
const path = join(repoRoot, ".agent-loop", "artifacts", runId, kind, safeName);
|
|
28
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
29
|
+
writeFileSync(path, content);
|
|
30
|
+
const record = {
|
|
31
|
+
id,
|
|
32
|
+
runId,
|
|
33
|
+
kind,
|
|
34
|
+
name: safeName,
|
|
35
|
+
path,
|
|
36
|
+
sha256: sha256(readFileSync(path)),
|
|
37
|
+
createdAt: new Date().toISOString()
|
|
38
|
+
};
|
|
39
|
+
storage.insertArtifact(record);
|
|
40
|
+
return record;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Create an artifact file that can be appended while work is still running. */
|
|
44
|
+
export function createArtifactWriter(
|
|
45
|
+
repoRoot: string,
|
|
46
|
+
storage: AgentLoopStorage,
|
|
47
|
+
runId: string,
|
|
48
|
+
kind: ArtifactKind,
|
|
49
|
+
name: string
|
|
50
|
+
): ArtifactWriter {
|
|
51
|
+
assertArtifactKind(kind);
|
|
52
|
+
const id = randomUUID();
|
|
53
|
+
const safeName = sanitizeName(name);
|
|
54
|
+
const path = join(repoRoot, ".agent-loop", "artifacts", runId, kind, safeName);
|
|
55
|
+
const hash = createHash("sha256");
|
|
56
|
+
let finalized: ArtifactRecord | undefined;
|
|
57
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
58
|
+
writeFileSync(path, "");
|
|
59
|
+
return {
|
|
60
|
+
id,
|
|
61
|
+
path,
|
|
62
|
+
append(content: string | Buffer): void {
|
|
63
|
+
if (finalized) {
|
|
64
|
+
throw new AgentLoopError("artifact_integrity_error", `Artifact writer is already finalized: ${id}`);
|
|
65
|
+
}
|
|
66
|
+
appendFileSync(path, content);
|
|
67
|
+
hash.update(content);
|
|
68
|
+
},
|
|
69
|
+
finalize(): ArtifactRecord {
|
|
70
|
+
if (finalized) {
|
|
71
|
+
return finalized;
|
|
72
|
+
}
|
|
73
|
+
finalized = {
|
|
74
|
+
id,
|
|
75
|
+
runId,
|
|
76
|
+
kind,
|
|
77
|
+
name: safeName,
|
|
78
|
+
path,
|
|
79
|
+
sha256: hash.digest("hex"),
|
|
80
|
+
createdAt: new Date().toISOString()
|
|
81
|
+
};
|
|
82
|
+
storage.insertArtifact(finalized);
|
|
83
|
+
return finalized;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Read an artifact and verify the stored sha256 digest before returning content. */
|
|
89
|
+
export function readArtifact(
|
|
90
|
+
storage: AgentLoopStorage,
|
|
91
|
+
artifactId: string
|
|
92
|
+
): { record: ArtifactRecord; content: Buffer } {
|
|
93
|
+
const record = readArtifactRecord(storage, artifactId);
|
|
94
|
+
if (!existsSync(record.path)) {
|
|
95
|
+
throw new AgentLoopError("artifact_integrity_error", `Artifact file is missing: ${record.id}`);
|
|
96
|
+
}
|
|
97
|
+
const content = readFileSync(record.path);
|
|
98
|
+
const actual = sha256(content);
|
|
99
|
+
if (actual !== record.sha256) {
|
|
100
|
+
throw new AgentLoopError("artifact_integrity_error", `Artifact sha256 mismatch: ${record.id}`, {
|
|
101
|
+
details: { expected: record.sha256, actual }
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return { record: toArtifactRecord(record), content };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** List artifacts for a run. */
|
|
108
|
+
export function listArtifacts(
|
|
109
|
+
storage: AgentLoopStorage,
|
|
110
|
+
runId: string
|
|
111
|
+
): ArtifactRecord[] {
|
|
112
|
+
return storage.listArtifacts(runId).map(toArtifactRecord);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Link a persisted artifact id to an event. */
|
|
116
|
+
export function linkArtifactToEvent(
|
|
117
|
+
storage: AgentLoopStorage,
|
|
118
|
+
eventId: string,
|
|
119
|
+
artifactId: string
|
|
120
|
+
): void {
|
|
121
|
+
storage.linkArtifactToEvent(eventId, artifactId);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function sha256(content: Buffer): string {
|
|
125
|
+
return createHash("sha256").update(content).digest("hex");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function sanitizeName(name: string): string {
|
|
129
|
+
return name.replaceAll("\\", "/").split("/").filter(Boolean).join("-");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function assertArtifactKind(kind: string): asserts kind is ArtifactKind {
|
|
133
|
+
if (!(ARTIFACT_KINDS as readonly string[]).includes(kind)) {
|
|
134
|
+
throw new AgentLoopError("storage_error", `Unsupported artifact kind: ${kind}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function readArtifactRecord(storage: AgentLoopStorage, artifactId: string): ReturnType<AgentLoopStorage["getArtifact"]> {
|
|
139
|
+
try {
|
|
140
|
+
return storage.getArtifact(artifactId);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (error instanceof AgentLoopError) {
|
|
143
|
+
throw new AgentLoopError("artifact_integrity_error", `Artifact metadata is unavailable: ${artifactId}`, {
|
|
144
|
+
details: { cause: error.message, code: error.code }
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function toArtifactRecord(record: {
|
|
152
|
+
id: string;
|
|
153
|
+
runId: string;
|
|
154
|
+
kind: string;
|
|
155
|
+
name: string;
|
|
156
|
+
path: string;
|
|
157
|
+
sha256: string;
|
|
158
|
+
createdAt: string;
|
|
159
|
+
}): ArtifactRecord {
|
|
160
|
+
return {
|
|
161
|
+
...record,
|
|
162
|
+
kind: record.kind as ArtifactKind
|
|
163
|
+
};
|
|
164
|
+
}
|