@kylecheng3146/agent-ops 0.10.0 → 0.11.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 +3 -2
- package/dist/packages/cli/src/args.js +18 -53
- package/dist/packages/cli/src/bin.js +28 -29
- package/dist/packages/cli/src/cli.js +25 -17
- package/dist/packages/cli/src/codex-loop-process.js +0 -6
- package/dist/packages/cli/src/commands/advance.js +2 -1
- package/dist/packages/cli/src/commands/parallel.js +1 -1
- package/dist/packages/cli/src/commands/review.js +8 -11
- package/dist/packages/cli/src/commands/run.js +12 -3
- package/dist/packages/cli/src/commands/task.js +6 -50
- package/dist/packages/cli/src/commands/verify.js +7 -3
- package/dist/packages/cli/src/context.js +22 -4
- package/dist/packages/cli/src/default-base.js +26 -0
- package/dist/packages/cli/src/hook-process.js +19 -83
- package/dist/packages/cli/src/office-entry.js +8 -4
- package/dist/packages/cli/src/owned-run-step.js +4 -4
- package/dist/packages/cli/src/parallel-deps.js +0 -2
- package/dist/packages/cli/src/run-deps.js +12 -9
- package/dist/packages/cli/src/run-entry.js +123 -19
- package/dist/packages/cli/src/run-setup-entry.js +2 -2
- package/dist/runtime/src/adapters/agy/config.js +2 -3
- package/dist/runtime/src/adapters/claude/config.js +2 -2
- package/dist/runtime/src/adapters/codex/config.js +2 -5
- package/dist/runtime/src/hooks/completion-gate.js +0 -15
- package/dist/runtime/src/install/harness.js +7 -4
- package/dist/runtime/src/install/plan.js +3 -3
- package/dist/runtime/src/install/profiles.js +0 -2
- package/dist/runtime/src/office/scene.js +1 -1
- package/dist/runtime/src/office/server.js +43 -16
- package/dist/runtime/src/office/snapshot.js +5 -2
- package/dist/runtime/src/parallel/finish.js +139 -65
- package/dist/runtime/src/parallel/manage.js +0 -3
- package/dist/runtime/src/parallel/service.js +5 -1
- package/dist/runtime/src/review/execute.js +77 -11
- package/dist/runtime/src/review/packet.js +17 -6
- package/dist/runtime/src/review/runner.js +2 -0
- package/dist/runtime/src/run/controls.js +99 -1
- package/dist/runtime/src/run/hosts/codex.js +28 -1
- package/dist/runtime/src/run/hosts/protocol.js +5 -2
- package/dist/runtime/src/run/integration.js +35 -13
- package/dist/runtime/src/run/policy.js +65 -10
- package/dist/runtime/src/run/scheduler.js +10 -5
- package/dist/runtime/src/run/service.js +68 -21
- package/dist/runtime/src/run/supervisor.js +11 -2
- package/dist/runtime/src/run/transport.js +1 -0
- package/dist/runtime/src/run/verification.js +2 -2
- package/dist/runtime/src/schema/validate.js +10 -5
- package/dist/runtime/src/security/permissions.js +85 -0
- package/dist/runtime/src/task/completion.js +4 -42
- package/dist/runtime/src/task/contract.js +11 -0
- package/dist/runtime/src/task/service.js +34 -57
- package/dist/runtime/src/task/store.js +3 -7
- package/dist/runtime/src/verify/acceptance-coverage.js +2 -3
- package/dist/runtime/src/verify/acceptance-protocol.js +4 -3
- package/dist/runtime/src/verify/acceptance-replay.js +4 -7
- package/dist/runtime/src/verify/service.js +6 -23
- package/dist/runtime/src/verify/source-fingerprint.js +17 -4
- package/docs/en/guides/configuration.md +8 -4
- package/docs/en/guides/task-debug.md +39 -121
- package/docs/en/spec/acceptance-and-evidence.md +31 -19
- package/docs/en/spec/harness-adapters.md +35 -12
- package/docs/en/spec/loop-engineering.md +40 -6
- package/docs/en/spec/review.md +6 -0
- package/docs/zh-TW/guides/configuration.md +6 -3
- package/docs/zh-TW/guides/task-debug.md +27 -66
- package/docs/zh-TW/spec/acceptance-and-evidence.md +19 -18
- package/docs/zh-TW/spec/harness-adapters.md +28 -9
- package/docs/zh-TW/spec/loop-engineering.md +14 -3
- package/docs/zh-TW/spec/review.md +5 -0
- package/package.json +1 -1
- package/schemas/config.schema.json +1 -21
- package/dist/packages/cli/src/debug-intake.js +0 -245
- package/dist/runtime/src/task/debug-intake.js +0 -15
- package/dist/runtime/src/task/debug-state.js +0 -129
- package/dist/runtime/src/task/debug-user-event.js +0 -95
- package/dist/runtime/src/task/debug.js +0 -612
|
@@ -1,76 +1,37 @@
|
|
|
1
1
|
# Task DEBUG 調查
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
DEBUG task 就是一般 task,只是驗收條件要證明原始回歸:一條 behavioral criterion,
|
|
4
|
+
其 `redCheckIds` 在 baseline commit 以 assertion 失敗、在 candidate 通過。除此之外沒有任何特殊流程。
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
```sh
|
|
7
|
+
agent-ops task create --debug --title "修購物車無法捲動" \
|
|
8
|
+
--goal '<原始問題,逐字>' \
|
|
9
|
+
--surface-file surface.json \
|
|
10
|
+
--criterion-file regression.json --criterion-file related.json
|
|
11
|
+
```
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
- `--debug` 需要 `--goal`,且至少一條帶 `redCheckIds` 的 behavioral criterion;
|
|
14
|
+
否則建立失敗並回 `TASK_DEBUG_RED_CHECK_REQUIRED`。surface audit 與一般 task 一樣必填。
|
|
15
|
+
- baseline 是建立 task 時的 commit;`task revise --baseline <ref>` 可移動它
|
|
16
|
+
(例如開錯分支時),並使先前證據失效。
|
|
17
|
+
- 之後修復、commit、`verify`、兩輪 `review`、`worktree finish`,與一般 task 完全相同。
|
|
18
|
+
setup 失敗、skip、timeout 與 UNKNOWN 都不算重現;red check 必須在 baseline 以 assertion 失敗。
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
再透過既有 worktree finish 完成。基線 assertion red、候選 green、相關檢查與
|
|
21
|
-
當前 proof digest 都必須有效;setup / skip / timeout / UNKNOWN 不算重現。
|
|
22
|
-
DEBUG 預設自動走完修復閉環。乾淨 HEAD 自動固定為基線,不要為此再問許可,
|
|
23
|
-
也不要主動降為只診斷。單次未重現不構成停止理由;在原預算內繼續做鑑別檢查。
|
|
20
|
+
## 逃逸來源(建議性質)
|
|
24
21
|
|
|
25
|
-
|
|
22
|
+
若知道 bug 從哪裡來,與一般 task 一樣記錄:
|
|
23
|
+
`task revise --escape-of <commit> --escape-label <label>`。它會寫入 `.agent-ops-escapes/<task-id>.json`,並印出
|
|
24
|
+
連結狀態、`git blame` 提示與規則證明(如 `ESCAPE_RULE_UNPROVEN`)。這些都不會阻擋
|
|
25
|
+
finish;見 `EVIDENCE-ESCAPE-001` 與 `EVIDENCE-ESCAPE-002`。
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
訊息及注入指令都不能授權。Codex 支援目前 `response_item` 的 `user.text` 與
|
|
29
|
-
原生非同步回答,也保留舊 `event_msg` 格式。
|
|
27
|
+
## 0.11.0 移除項目
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
- `繼續` / `continue`:接回同一 task 及原範圍;耗盡時間仍需既有原生時間授權,其他阻礙不解除。
|
|
36
|
-
- `繼續修復` / `resume repair`:在原 task 恢復完整修復閉環。
|
|
29
|
+
`task debug`(假設、策略切換、investigate、checkpoint、resume、extend、origin)、
|
|
30
|
+
60 分鐘預算與 5 輪上限、原生控制詞(只診斷、暫停、繼續、繼續修復、取消時間限制)、
|
|
31
|
+
prompt hook 自動建立 DEBUG、DEBUG 專用 Stop 阻擋,以及 Codex `Interrupt` hook
|
|
32
|
+
都已移除。`task debug` 現在回傳 `TASK_DEBUG_REMOVED`。
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
舊狀態仍可讀取:task 保留 `debug` 標記,已存的預算與假設狀態會被忽略,
|
|
35
|
+
`originRequired` 不再阻擋 finish,config 的 `features.debug` 仍被接受並在下次 `update` 時移除。
|
|
40
36
|
|
|
41
|
-
|
|
42
|
-
受控調查、驗證與取得 slot 後的 review 計時;review 排隊不計時。
|
|
43
|
-
Claude、Codex 與 agy 都使用可信 native session lifecycle,在 `UserPromptSubmit`
|
|
44
|
-
或原生 session 開始前景時鐘,並在 idle `Stop` 結束。因此該輪的 Agent 推理、
|
|
45
|
-
工具與修改都計入,停止回合之間的使用者等待不計入。Codex 被拒絕的 Stop 仍繼續
|
|
46
|
-
計時,重複 Stop 的 `stop_hook_active: true` 不會繞過 DEBUG gate。opencode 沒有支援
|
|
47
|
-
的前景 lifecycle,不可宣稱完整的 active-time 預算。
|
|
48
|
-
中斷且沒有封存結果的操作,必須等持久化 process identity 證實程序已死亡後,
|
|
49
|
-
才封存 UNKNOWN environment receipt;這段經過時間仍會保留,不會退還不明時間。
|
|
50
|
-
測試材料修改不算修復輪;兩輪停滯需要一次實質策略切換,再兩輪停滯停止。
|
|
51
|
-
|
|
52
|
-
假設確認後以 `origin --payload-file <json>` 宣告 bug 來源:`{"kind": "escape-of",
|
|
53
|
-
"commit": "<commit>", "label": "<最早關卡>"}`(可選 `"receipt"`),或 `{"kind":
|
|
54
|
-
"pre-existing" | "external", "reason": "..."}`。它會修訂 contract,因此 review 前要重新
|
|
55
|
-
verify 並重新記錄已確認的假設;沒有它時 finish 以 `ESCAPE_ORIGIN_MISSING` 拒絕。
|
|
56
|
-
`escape-of` 會印出連結狀態與 `git blame` 提示,並寫入 `.agent-ops-escapes/<task-id>.json`,
|
|
57
|
-
與修復一起 commit(見 `EVIDENCE-ESCAPE-001`)。surface 標記還需要一條在被逃逸 commit
|
|
58
|
-
重放成立、受追蹤的 `.agent-ops-escapes/rules/<rule-id>.json` 規則;criteria/verify 標記需要
|
|
59
|
-
`"promote": "<runnerId:checkId>"`;否則 finish 以 `ESCAPE_RULE_UNPROVEN` 拒絕(見
|
|
60
|
-
`EVIDENCE-ESCAPE-002`)。
|
|
61
|
-
|
|
62
|
-
checkpoint 保存 task/session/source/contract/config/cause;resume 只接受同一
|
|
63
|
-
擁有者 worktree 的匹配 checkpoint,保留 dirty 內容及預算。失配需要人類確認。
|
|
64
|
-
時間耗盡後明確「繼續」可透過 `extend --event-id latest` 讀取原生人類事件,
|
|
65
|
-
一次增加 20 分鐘;沒有可信事件来源則回報 unsupported,其他阻礙不被解除。
|
|
66
|
-
|
|
67
|
-
Claude / agy / 原生 Codex 的 Stop 都會檢查無 Git 差異的 active DEBUG task,不論是否安裝 completion gate。
|
|
68
|
-
DEBUG gate 故障先針對 task/config 重新載入一次;仍失敗則明確回報 `UNKNOWN/未完成`
|
|
69
|
-
與 `agent-ops doctor` 恢復方法,允許非成功停止並保留 task、證據及預算,不無限重試。
|
|
70
|
-
有效停止 checkpoint 只允許非成功停止,task 仍 active;allow-stop 不能繞過。
|
|
71
|
-
Codex 的一般 completion gate 仍 unsupported;DEBUG 專用 Stop 以原生
|
|
72
|
-
`decision: "block"` 與 reason 續跑,不能使用會停止 harness 的 `continue: false`。
|
|
73
|
-
已接受的 Stop 結束前景時鐘;opencode Stop unsupported。
|
|
74
|
-
只承諾前景對話與持久化進度,不承諾對話關閉後繼續,也不宣稱抵擋同檔案權限攻擊。
|
|
75
|
-
|
|
76
|
-
完整命令及細節見英文 [Task DEBUG](../../en/guides/task-debug.md)。
|
|
37
|
+
完整說明見英文 [Task DEBUG](../../en/guides/task-debug.md)。
|
|
@@ -12,15 +12,17 @@
|
|
|
12
12
|
|
|
13
13
|
- `behavioral`:指定 red checks 在 baseline 都因斷言失敗,candidate 的必要 checks 全部通過。`invariant`:candidate 的必要 checks 通過,baseline 原本 PASS 有效。`review-only`:兩輪 fresh review 對照原始目標驗收,不宣稱機械證明。
|
|
14
14
|
- repo 授權的 `verification.acceptanceRunners` 定義 argv、setup、產品與 test build。task 提供穩定 check IDs 與明列且已 commit 的 test/fixture/helper;兩側各自安裝並 build 其版本,再覆蓋 candidate 材料。task 不能改 runner 命令。
|
|
15
|
+
- generic runner 必須輸出共用相同 runner、baseline 與材料之 criteria 所要求的每個 `checkId`,且各恰好一次;缺少或重複為 UNKNOWN。其他列會被忽略,因此 binding 可以只列 red checks。未要求的列若失敗,runner 的 exit code 仍與 PASS 彙總矛盾,candidate 會成為 UNKNOWN,絕不會 green。
|
|
15
16
|
- fixture、collection、build、timeout、取消、輸出截斷、缺少或重複 ID、retry、skip MUST NOT 算 red 或 green。完整 execution artifacts 以摘要綁定契約、材料與 commits。
|
|
16
17
|
- 原生 Jest JSON 與 Vitest task errors 無法完整區分 test-call 斷言與 hook/fixture cleanup。除非已 review 的 repo collector 提供明確斷言分類,失敗列 MUST 保持 UNKNOWN;AssertionError 名稱、matcher 資料與 pass 的 hook 狀態都不足。green 需要明確的單次 attempt 證據:attempts、Jest invocations,或 Vitest retryCount 與 repeatCount 兩者。原生 Vitest JSON 省略這些 counters,不能單獨提供機械證明。pytest 每個 nodeid 必須有一組依序的 setup/call/teardown;Rust protocol 必須提供 attempts。缺 metadata MUST NOT 預設為一次成功。
|
|
17
|
-
- baseline
|
|
18
|
+
- baseline 為建立 commit;finding pin 使用被 FAIL 的 candidate。`task revise --baseline <ref>` 會一次把所有 typed criterion 的 baseline(以及 task 的建立 commit)移到 HEAD 的某個祖先 commit,例如 task 開在錯的分支時,並使其驗證失效。revise/replan 保留原始目標、全部映射要求與先前契約/證據。
|
|
19
|
+
- verification evidence 只綁定 criteria 的 execution hash。只改 surface 或 origin 的 revise 會保留它;改 criteria 或設定則使其失效。0.11 之前的證據綁定完整 contract hash,契約未變時仍相符。每個新契約都需要兩輪 fresh review。
|
|
18
20
|
- `task pin-finding` 驗證已保存 report 的 digest/index 來源。recurrence 明確引用既有 pin。第六條回傳 replan 義務且不修改狀態;映射後必須保留 pin 與全部要求。
|
|
19
21
|
- reviewer packet 包含原始目標、修訂歷史、coverage 與 verifier 材料,審查斷言本身與明確的 fallback 理由。機械 green 不會省略必要 policy commands 或任一 fresh review。
|
|
20
22
|
|
|
21
23
|
task state 寫入 version 2、讀取 version 1;evidence version 4 讀取舊證據,attestation version 3 綁定契約與 coverage。legacy criteria 維持下列 verifier 規則;typed/revised 契約不能沿用舊 review。coverage 分列機械證明、review-only、legacy;runner 綁定本身不算證明。明確 no-change proof 列出固定 candidate 上已 commit 的 regular source files;整合時重新解析支持路徑。一般 empty-diff review 與 empty-branch direct finish 仍拒絕。
|
|
22
24
|
|
|
23
|
-
English source version: 2026-10-
|
|
25
|
+
English source version: 2026-10-20. Revalidate: when the English specification changes.
|
|
24
26
|
|
|
25
27
|
## EVIDENCE-CRITERION-001
|
|
26
28
|
|
|
@@ -46,7 +48,7 @@ English source version: 2026-10-06. Revalidate: when the English specification c
|
|
|
46
48
|
|
|
47
49
|
每個新 task MUST 在寫驗收條件前記錄變更還會影響哪些位置(surface audit);discovery 找到未分類位置時,review MUST NOT 執行。
|
|
48
50
|
|
|
49
|
-
- Trigger: 建立 task(`task create
|
|
51
|
+
- Trigger: 建立 task(`task create`,含 `--debug`,或 run plan)、修訂 surface,或審查 task。
|
|
50
52
|
- Action: 以記錄下來的固定字串查詢或 `n/a: 理由` 搜尋四個軸向,把每個命中分類為 included、excluded(glob + 理由)或 UNKNOWN,每列 surface 都寫出可觀察的 `behavior`,並宣告每個共享契約為 compatible 或 breaking。以 `task create|revise --surface-file <json>` 提交。
|
|
51
53
|
- Evidence: surface 存在 task 上、綁進 contract hash 並保留修訂歷史,review 前以 `git grep -F` 重放,並以 `surface-coverage` 條件交給兩位 reviewer。
|
|
52
54
|
- Positive: `diff token 命中三個複製貼上的同類頁面;review 前 audit 已把它們連同行為列入 included。`
|
|
@@ -82,7 +84,8 @@ English source version: 2026-10-06. Revalidate: when the English specification c
|
|
|
82
84
|
至多 128 筆。Reviewer 會看到每筆排除與其命中數,若它隱藏了先前
|
|
83
85
|
`SURFACE_UNCLASSIFIED` 阻擋所列的檔案則會標示出來。
|
|
84
86
|
結果以 HEAD、base、diff、查詢集合與 config hash 快取。
|
|
85
|
-
-
|
|
87
|
+
- `verify` 在驗證後執行同一個 gate,並以建議性質的輸出列印結果(`Surface: PASS`,或錯誤碼與未分類路徑),讓 surface 在 review 前修好;只有 review 會強制。
|
|
88
|
+
- Review 以 NOT_RUN 回報:`SURFACE_MISSING`(沒有或無法解析的 surface;
|
|
86
89
|
task 建立時也會拒絕)、`SURFACE_UNCLASSIFIED`、`SURFACE_QUERY_TOO_BROAD`(單一
|
|
87
90
|
記錄查詢超過 200 個檔案)、`SURFACE_EXCLUSION_TOO_BROAD`(單一 glob 超過 10 個
|
|
88
91
|
檔案,或包含 diff 內的檔案)、`SURFACE_DISMISSAL_TOO_BROAD`(超過 128 筆排除)、
|
|
@@ -99,17 +102,15 @@ English source version: 2026-10-06. Revalidate: when the English specification c
|
|
|
99
102
|
|
|
100
103
|
## EVIDENCE-ESCAPE-001
|
|
101
104
|
|
|
102
|
-
|
|
105
|
+
bug 修復在已知來源時 SHOULD 宣告 bug 的來源;宣告的逃逸 MUST 以機械方式連結、標記並記錄。此宣告屬建議性質(advisory),絕不阻擋 finish。
|
|
103
106
|
|
|
104
|
-
- Trigger: DEBUG
|
|
105
|
-
- Action: 以 `task
|
|
106
|
-
- Evidence: `origin` 綁進 task contract hash
|
|
107
|
-
- Positive:
|
|
108
|
-
- Negative:
|
|
107
|
+
- Trigger: DEBUG task(`task create --debug`)或任何 task 修復一個通過了先前關卡的 bug,且已知其來源。
|
|
108
|
+
- Action: 以 `task create|revise --escape-of <commit> --escape-label <label> [--escape-receipt <id>]` 宣告 `origin`,DEBUG 與一般 task 相同。0.10 `task debug` 記錄的 `pre-existing` 與 `external` origin(附 `reason`)仍可讀取;未宣告的 origin 另行計數。把 `.agent-ops-escapes/<task-id>.json` 記錄與修復一起 commit。
|
|
109
|
+
- Evidence: `origin` 綁進 task contract hash;兩位 reviewer 在 `escape-label` 條件下收到連結狀態、標記、事實與 residual risks。沒有 origin 的 task 仍可 finish;0.10 標記 `originRequired` 的 task 不再被拒絕(`ESCAPE_ORIGIN_MISSING` 已於 0.11.0 移除)。
|
|
110
|
+
- Positive: `回歸在 baseline 為紅、修復後為綠;origin escape-of 864dbbe2 label surface;重放把漏掉的頁面標為 surface-unseen;記錄與修復一起 commit。`
|
|
111
|
+
- Negative: `把 surface-excluded 的漏網改標成 review,理由是 reviewer「應該注意到」。`
|
|
109
112
|
|
|
110
|
-
-
|
|
111
|
-
contract revision:重新 verify、以新證據重新記錄已確認的假設,然後 review。重新宣告會
|
|
112
|
-
取代記錄。輸出會列出修復所改或刪行的 `git blame` 提示 commit;blame 只是提示,因為遺漏
|
|
113
|
+
- 宣告是一次 contract revision:重新 verify,然後 review。重新宣告會取代記錄。輸出會列出修復所改或刪行的 `git blame` 提示 commit;blame 只是提示,因為遺漏
|
|
113
114
|
型 bug 存在於沒人寫過的行。
|
|
114
115
|
- finish receipt 留在本機 Git common directory。Schema 2 記錄 target..candidate 的每個
|
|
115
116
|
commit 與逐檔 diff fingerprint(去掉 blob id 與 hunk 位置,squash merge 後仍相同);
|
|
@@ -133,13 +134,13 @@ English source version: 2026-10-06. Revalidate: when the English specification c
|
|
|
133
134
|
|
|
134
135
|
## EVIDENCE-ESCAPE-002
|
|
135
136
|
|
|
136
|
-
surface 逃逸
|
|
137
|
+
surface 逃逸 SHOULD 變成已證明、受追蹤的規則;學到的規則 MUST 只能收緊 surface gate。此證明屬建議性質,絕不阻擋 finish。
|
|
137
138
|
|
|
138
139
|
- Trigger: 已宣告的 escape-of origin(EVIDENCE-ESCAPE-001),或以 `task create --escape-of <commit> --escape-label <label> --escape-fix <fix-commit>` 回填的過去逃逸。
|
|
139
|
-
- Action: 標記為 `surface`、`surface-unseen` 或 `surface-excluded` 時,commit 一條規則 `.agent-ops-escapes/rules/<rule-id>.json`,形狀為 `{"id", "escape", "label", "trigger": {"paths", "tokens"}, "expand": {"paths", "queries"}, "noExclude"}`,並重新宣告 origin。`criteria` 或 `verify` 以 `--escape-promote <runnerId:checkId>`(
|
|
140
|
-
- Evidence:
|
|
140
|
+
- Action: 標記為 `surface`、`surface-unseen` 或 `surface-excluded` 時,commit 一條規則 `.agent-ops-escapes/rules/<rule-id>.json`,形狀為 `{"id", "escape", "label", "trigger": {"paths", "tokens"}, "expand": {"paths", "queries"}, "noExclude"}`,並重新宣告 origin。`criteria` 或 `verify` 以 `--escape-promote <runnerId:checkId>`(origin 欄位 `"promote"`)指名修復 task 由紅轉綠的 check。其他標記記錄為 lesson。
|
|
141
|
+
- Evidence: 宣告 origin 時執行證明,失敗時印出 `ESCAPE_RULE_UNPROVEN`、`ESCAPE_RULE_TOO_BROAD` 或 `SURFACE_BUDGET_EXCEEDED`;自 0.11.0 起 finish 不再因此拒絕。ledger 記錄保存證明用規則、其內容 hash 與回測的 fires、commits 與比率。
|
|
141
142
|
- Positive: `逃逸 surface-unseen;規則 image-size-siblings 在被逃逸 commit 上觸發,其查詢在該 commit 命中兩個漏掉的兄弟頁面,且在最近 200 個 commit 中只觸發 2 次。`
|
|
142
|
-
- Negative: `規則從未在被逃逸 commit
|
|
143
|
+
- Negative: `規則從未在被逃逸 commit 上觸發卻把 surface 逃逸報為已證明,或新增可排除路徑的規則欄位。`
|
|
143
144
|
|
|
144
145
|
- 規則只含資料且不需要 trust:trigger 與 expand 變成 argv `git grep -F` 查詢與路徑
|
|
145
146
|
glob,絕不經過 shell 或 regex。嚴格 schema 沒有任何能移除查詢、新增排除或放寬關卡的
|
|
@@ -202,7 +203,7 @@ surface 逃逸 MUST 在修復 finish 前變成已證明、受追蹤的規則;
|
|
|
202
203
|
|
|
203
204
|
既有 task 檔案仍可直接讀取,不需資料格式遷移。缺少 config baseline、使用任意文字證據,或含有未完成但已封存子任務的舊任務,必須重建並完成驗證與審查。重複執行 complete 也會重新驗證證據;舊的 completed 狀態本身不夠。Runtime 呼叫端必須提供 `TaskServiceOptions.completion`;缺少此 repository context 時,complete 會拒絕執行,但仍可讀取任務。
|
|
204
205
|
|
|
205
|
-
Commit 前,verify、review、complete 使用預設 worktree scope。Commit 後,三個命令都使用相同的 `--base <git-ref
|
|
206
|
+
Commit 前,verify、review、complete 使用預設 worktree scope。Commit 後,三個命令都使用相同的 `--base <git-ref>`,為已提交範圍重新產生證據。未指定 `--base` 且 worktree 乾淨時,verify 與 review 自動選擇:session worktree 使用與目標分支的 merge-base(finish rebase 後會跟著移動),其他情況使用 task 的建立 commit(須為 HEAD 的祖先)。Base 模式的 source fingerprint 忽略 escape ledger 的 task 記錄(`.agent-ops-escapes/task-*.json`;rules 仍納入 fingerprint),因此 commit ledger 不會使驗證與 review 失效;0.10 計算的 fingerprint 不同,升級後需重新 verify 一次;runtime 呼叫端使用 `TaskServiceOptions.completion.base`。Base 模式要求 worktree 乾淨。沒有 worktree 變更又未指定 base 時,complete 會回傳 `TASK_COMPLETION_SCOPE_REQUIRED`。agy Stop gate 仍檢查 worktree scope 的證據:請在 commit 前完成並停止,或由使用者檢查已完成的 base-scope 任務後,核准一次性的 Stop permit。
|
|
206
207
|
|
|
207
208
|
這項機制強制的是 toolkit 的完成命令,無法阻止 host 直接用自然語言宣稱完成,也無法阻止具有相同檔案權限的 process 直接改寫 task/evidence 檔案;它不會鎖住 Git 或其他來源檔案寫入者。
|
|
208
209
|
|
|
@@ -93,7 +93,7 @@ script,也不得改變一般 permission request。
|
|
|
93
93
|
| office-presence (Preview) | degraded | degraded | supported | degraded |
|
|
94
94
|
| lifecycle-summary | degraded | supported | supported | degraded |
|
|
95
95
|
| command-policy | supported | unknown | supported | supported |
|
|
96
|
-
| completion-gate | supported | unsupported |
|
|
96
|
+
| completion-gate | supported | unsupported | supported | unsupported |
|
|
97
97
|
| optional-stop-verify | degraded | unsupported | supported | degraded |
|
|
98
98
|
|
|
99
99
|
Runtime-failure 處理中,只有 `command-policy` 為 fail-closed。當已安裝的 config
|
|
@@ -101,14 +101,11 @@ Runtime-failure 處理中,只有 `command-policy` 為 fail-closed。當已安
|
|
|
101
101
|
OpenCode `tool.execute.before` plugin 可在其支援的 Bash surface 上 throw 文件化的
|
|
102
102
|
denial 或 unavailable-runtime error。Codex 維持 `unknown` 且絕不輸出 denial。
|
|
103
103
|
Fixture test 只斷言這些 wire 與 plugin shape;它們不證明 host 會實際遵守 denial。
|
|
104
|
-
`SessionStart`
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
Codex
|
|
108
|
-
|
|
109
|
-
會停止。`stop_hook_active` 表示已經續跑過,不能繞過 DEBUG 檢查。
|
|
110
|
-
原生 `Interrupt` 暫停採三秒 timeout,不得阻擋或重啟人類中斷。
|
|
111
|
-
此 DEBUG 專用政策不會啟用 Codex 的一般 completion gate 或 optional Stop verifier。
|
|
104
|
+
`SessionStart` 與 `Stop` failure 維持 fail-open。
|
|
105
|
+
|
|
106
|
+
自 0.11.0 起沒有 DEBUG 專用 hook:prompt 不會開 DEBUG task,Stop 不會因 DEBUG 擋住
|
|
107
|
+
session,Codex `Interrupt` 不再註冊(舊安裝殘留的註冊在 `update` 前為 no-op)。Codex
|
|
108
|
+
仍沒有一般 completion gate 或 optional Stop verifier。
|
|
112
109
|
Codex 另以每個原生 hook 的精確定義保存信任。更新受管理 hook 後,必須在 `/hooks`
|
|
113
110
|
審核新增或變更的定義;agent-ops repository trust 不會啟用尚未審核的原生 hook。
|
|
114
111
|
|
|
@@ -164,6 +161,28 @@ byte-identical。Update MUST 保留此 profile,除非提供 `--auto-run on|off
|
|
|
164
161
|
- Positive: `啟用 run profile 的 Claude Code session 在 main checkout 寫入 .agent-ops/state/run-goal.md,並在 background 啟動 agent-ops run --host claude --wait。`
|
|
165
162
|
- Negative: `run profile 啟用時仍將八項 criteria 的變更拆成 subtask、在 GEMINI.md 加入 auto-run 文字,或未經使用者即 resume blocked run。`
|
|
166
163
|
|
|
164
|
+
`run status` 的 JSON 提供 `recovery`,文字輸出也呈現相同資訊:phase(舊紀錄為
|
|
165
|
+
unknown)、有界且遮蔽敏感資料的原始診斷、sealed proof(`recorded-unchecked`
|
|
166
|
+
僅表示紀錄存在,不代表驗證通過)、cleanup 狀態、能否 resume 與具體修復方式。
|
|
167
|
+
相同 cleanup blocker 保留原始 cause。Trust、native identity、generation 與 denial
|
|
168
|
+
仍 fail-closed;sealed proof 不得抹除 native 或組織的明確拒絕。
|
|
169
|
+
`run status` 與 `run logs` 的 JSON `state` 省略 event 歷史與 goal 全文,保留
|
|
170
|
+
`eventCount` 與 `goalHash`,讓重複輪詢維持精簡;event 本身由 `run logs` 回傳。
|
|
171
|
+
|
|
172
|
+
人工 resume 已寫入 receipt 的 integration,必須先驗證原 sealed proof、receipt
|
|
173
|
+
digest 與 transaction、owner/generation、candidate/target ancestry 及 Git note。
|
|
174
|
+
只執行尚缺的 note、gate、trust 與 checkout cleanup,保留原 receipt bytes 與已完成的
|
|
175
|
+
task/proof。尚未記錄完成且缺失的 note 可補寫;已記錄完成的 note 缺失或變更則阻擋。
|
|
176
|
+
確認所屬 checkout 已移除後才可標記 cleaned,不再次移動 target。Target 若已前進,
|
|
177
|
+
僅在它是 receipt-bound candidate 的後代且有原整合歷史時接受。
|
|
178
|
+
|
|
179
|
+
Cleanup 保存有界且遮蔽敏感資料的原始錯誤,以及 transaction boundary、checkout
|
|
180
|
+
status、untracked file metadata 與 heartbeat liveness 的 retry-input digest。
|
|
181
|
+
相同輸入回 `RUN_CLEANUP_UNCHANGED`,不重複 cleanup;修復條件改變後仍須重新通過所有
|
|
182
|
+
安全檢查,才可嘗試一次。此 digest 僅防止盲重試,不是 proof reuse 或新授權。
|
|
183
|
+
Finalization 失敗不立即重跑同一 integration,保留原 cause 供人工恢復。
|
|
184
|
+
Live/unknown/偽造/symlink heartbeat 與無關使用者檔案仍受既有 non-force removal 保護。
|
|
185
|
+
|
|
167
186
|
## HARNESS-ADAPTER-008
|
|
168
187
|
|
|
169
188
|
`agent-ops office`(Preview)MUST 只依據 agent-ops state 以 read-only 方式呈現進度。
|
|
@@ -11,17 +11,28 @@
|
|
|
11
11
|
- Negative: 只因 native goal completion 就把 run 標為 complete。
|
|
12
12
|
|
|
13
13
|
- macOS supervisor 在啟動 goal 前保存 Claude/Codex native 身份。同 host writers 使用獨立 worktrees 與目前 generation lease;coordinator 計入最多兩個 writer。依賴以固定交付提供,child baseline 在依賴 commits 進入 checkout 後建立。
|
|
14
|
-
-
|
|
14
|
+
- 新 run 不設總時間上限(`budget.limitMs: null`),仍以 active intervals 聯集記錄 setup、verify、review、整合的消耗。舊有限 run 僅在下次人工 resume 升級,保留舊上限、intervals、消耗、usage epochs 與 blocker 歷史;讀取或安裝不會升級或喚醒舊 run。單次命令/review timeout、provider quota、無進度與 pause/stop 控制保持獨立有效。明確指定 --time-budget 會被拒絕。缺少計數維持 UNKNOWN,partial usage 不算完整成本。
|
|
15
15
|
- 實質疑問暫停並要求明確回答。修復保留目標與失敗紀錄;連續兩次相同失敗且無進展停止該 worker 及其依賴,獨立 task 仍可執行。review findings 在下次 final gate 前必須有回歸 pin 或記錄 review-only fallback。
|
|
16
16
|
- native completion 僅是觀察。完成必須有必要驗證、兩輪 fresh review、完成的 task state、綁定 target 的 receipt。整合在移動 target 前封存 candidate,記錄 task、receipt、note、cleanup 進度;恢復驗證封存證據,不重做成功的 target mutation。不一致的 target 維持 blocked。
|
|
17
17
|
- Stop 先保存禁止續跑,再確認受管 process group 死亡。crash recovery 不得取代仍存活或身份不明的 writer。dirty checkout、版本漂移、restart storm 保留現場。重開機/login 改變必須明確 resume;不修改全域 host policy。
|
|
18
18
|
- Native handshake 前先保存啟動程序身份;尚未開啟的 Codex session 必須證明程序死亡,才可在新 lease 重新初始化。
|
|
19
19
|
|
|
20
20
|
- Setup 與 native agent 發起的 local proof MUST 先登記 dormant process group 再執行;Stop 也要取消子程序。中斷的 setup 必須明確 resume,保留 checkout 與失敗 artifact。
|
|
21
|
-
- Coordinator 只能在綁定原始 repository trust、可執行 runtime
|
|
22
|
-
-
|
|
21
|
+
- Coordinator 只能在綁定原始 repository trust、可執行 runtime、一小時有限授權期限的 run-scoped policy 下,評估新增固定且非危險的能力。不得繞過 native 或組織的明確拒絕。Policy 變更先停止所有 writer,保留固定交付,以 journal 記錄 contract 同步,再啟動新 generation;不授予永久 repository trust。Native decision 保存遮罩後的命令範圍與理由,provider 未提供的證據維持 UNKNOWN。
|
|
22
|
+
- 執行中的 run 僅在原始 repository trust、config、可執行 runtime 與操作範圍完全相同時,自動續期過期的有限授權。續期以 digest 綁定前一版本並保留既有評估,拒絕無效/倒退時鐘;並行 stop、denial 或 scope 變更使續期失敗。權限已變更或撤銷時須沿既有 trust/resume 流程取得真正授權;續期不授予永久 trust,也不消除 denial。明確 resume 先核對不可變 renewal lineage,再同步 policy。回答問題也沿用相同恢復檢查;兩者皆不得復活已被明確拒絕的工作。
|
|
23
|
+
- `run resume <id> --retry-approval <auth-event-id>` MAY 明確重試以額度不足 wrapper 失敗的 Codex 自動審批。每筆指定失敗 MUST 保有原始 private artifact、digest、worker identity 與 policy lineage;真正或未指定的拒絕仍阻擋。多筆失敗可重複旗標。成功確認會留存並在後續 resume 重新驗證;此操作不授予命令權限,原生審批必須重跑,既有 writer 死亡、checkout、trust 與預算檢查仍適用。
|
|
24
|
+
- 當次人工 resume 的初始恢復 MAY 忽略啟用前收到、且 native 更新秒數嚴格早於該次 resume 的本執行緒 Codex 額度快照。自動重啟、未知/同秒/未來時間、時鐘倒退,以及啟用開始後收到的所有額度事件仍有效。Native 啟用 MUST 確認 goal 為 active,才能啟動或轉向 turn。人工 resume 可解除衍生的 `RUN_POLICY_DISABLED` task 阻擋,但保留 ID、耗用、歷史及其他產品失敗。
|
|
23
25
|
- 收斂判斷比較實際 per-check 狀態及未解除的 finding pins;source fingerprint、時間或診斷文字本身不能算進展。
|
|
24
26
|
|
|
27
|
+
不論 `jobs` 為何,coordinator MUST 只在規劃完 child 後送出明確的 `action=wait`
|
|
28
|
+
request 才讓出;supervisor 不會停止仍在規劃的 coordinator。wait 會確認 coordinator
|
|
29
|
+
程序死亡、釋出其 slot 並捨棄其 native thread,使 coordinator 在 child 工作期間
|
|
30
|
+
不再輪詢 `run status`。恢復流程讓等待中的 coordinator 保持停止;所有 child 皆為
|
|
31
|
+
delivered、complete 或 blocked 後,才以新 generation 在全新 native session 中接續,
|
|
32
|
+
instructions 會列出每個 child 的狀態與交付 commit。`jobs=1` 時,child MUST 等該
|
|
33
|
+
程序確認死亡後才執行。native 註冊、恢復與啟動 MUST 都限制最多 `jobs` 個 active
|
|
34
|
+
agent;僅扣除 task slot 不足以符合。
|
|
35
|
+
|
|
25
36
|
English source version: 2026-07-23. Revalidate: when the English specification changes.
|
|
26
37
|
|
|
27
38
|
## LOOP-START-001
|
|
@@ -12,6 +12,8 @@ English source version: 2026-09-30. Revalidate: when the English specification c
|
|
|
12
12
|
- Positive: `Packet 包含已記錄的 task 意圖、條件、檔案與證據要求。`
|
|
13
13
|
- Negative: `轉送完整 session transcript 與環境。`
|
|
14
14
|
|
|
15
|
+
大型 surface audit(含完整修訂歷史、discovery 與 post-block exclusions)可使用 digest-bound reference,指向已驗證 snapshot 契約 manifest 中正規化後完全相符的資料。兩輪審查都必須讀取完整 manifest surface。64 KiB inline packet 與 512 KiB manifest 上限仍有效;小型及舊版 packet 保留 inline 資料。
|
|
16
|
+
|
|
15
17
|
## REVIEW-RESULT-001
|
|
16
18
|
|
|
17
19
|
審查結果 MUST 保留 PASS、FAIL 或 NOT_RUN,且 MUST NOT 把 NOT_RUN 轉成 PASS。
|
|
@@ -58,6 +60,9 @@ tree,不 fallback、不寫 attestation;timeout 保留獨立 NOT_RUN reason
|
|
|
58
60
|
|
|
59
61
|
## REVIEW-CHAIN-001
|
|
60
62
|
|
|
63
|
+
agy 在生成期間可能完全沒有輸出。review 與接續回答 MUST NOT 僅因沒有輸出而
|
|
64
|
+
被取消;設定的單輪 timeout 與呼叫者取消仍然有效。
|
|
65
|
+
|
|
61
66
|
兩個規劃的 session MUST 依序為必要 reviewer 與 adversarial 複審。第一個
|
|
62
67
|
session 遇到 FAIL 或 NOT_RUN MUST 立即停止;不會在該結果後 fallback。
|
|
63
68
|
|
package/package.json
CHANGED
|
@@ -282,27 +282,7 @@
|
|
|
282
282
|
},
|
|
283
283
|
"debug": {
|
|
284
284
|
"type": "object",
|
|
285
|
-
"
|
|
286
|
-
"required": [
|
|
287
|
-
"enabled",
|
|
288
|
-
"budgetMs",
|
|
289
|
-
"maxRounds"
|
|
290
|
-
],
|
|
291
|
-
"properties": {
|
|
292
|
-
"enabled": {
|
|
293
|
-
"type": "boolean"
|
|
294
|
-
},
|
|
295
|
-
"budgetMs": {
|
|
296
|
-
"type": "integer",
|
|
297
|
-
"minimum": 1,
|
|
298
|
-
"maximum": 86400000
|
|
299
|
-
},
|
|
300
|
-
"maxRounds": {
|
|
301
|
-
"type": "integer",
|
|
302
|
-
"minimum": 1,
|
|
303
|
-
"maximum": 100
|
|
304
|
-
}
|
|
305
|
-
}
|
|
285
|
+
"description": "Pre-0.11 DEBUG budget settings; accepted, ignored and dropped by update."
|
|
306
286
|
}
|
|
307
287
|
}
|
|
308
288
|
},
|
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
2
|
-
import { TaskService } from "../../../runtime/src/task/service.js";
|
|
3
|
-
import { FileTaskStore } from "../../../runtime/src/task/store.js";
|
|
4
|
-
import { calculateConfigHash } from "../../../runtime/src/config/hash.js";
|
|
5
|
-
import { evaluateGuardrail } from "../../../runtime/src/guardrails/evaluate.js";
|
|
6
|
-
import { isDebugIssue } from "../../../runtime/src/task/debug-intake.js";
|
|
7
|
-
import { ensureSessionWorktree, readWorktreeRecord, sessionWorktreeName } from "../../../runtime/src/parallel/service.js";
|
|
8
|
-
import { listWorktrees, resumeWorktree } from "../../../runtime/src/parallel/manage.js";
|
|
9
|
-
import { collectChangeSurface } from "../../../runtime/src/verify/change-surface.js";
|
|
10
|
-
import { calculateSourceFingerprint } from "../../../runtime/src/verify/source-fingerprint.js";
|
|
11
|
-
import { AgentOpsError } from "../../../runtime/src/fs/paths.js";
|
|
12
|
-
import { loadEffectiveConfig } from "./context.js";
|
|
13
|
-
import { gitRunner, worktreeDependencies } from "./parallel-deps.js";
|
|
14
|
-
const foregroundClockSupported = (host) => host !== "opencode";
|
|
15
|
-
function nativeSessionId(input) {
|
|
16
|
-
const fromInput = [input.session_id, input.sessionId, input.conversationId, input.thread_id, input.threadId]
|
|
17
|
-
.find((value) => typeof value === "string" && value.length > 0);
|
|
18
|
-
return fromInput ?? [process.env.AGENT_OPS_SESSION_ID, process.env.CODEX_THREAD_ID]
|
|
19
|
-
.find((value) => value !== undefined && value.length > 0);
|
|
20
|
-
}
|
|
21
|
-
/** Start only an already attached DEBUG issue; native hooks never create work. */
|
|
22
|
-
export async function startDebugSession(root, sessionId, host) {
|
|
23
|
-
if (!foregroundClockSupported(host) || sessionId.length === 0)
|
|
24
|
-
return;
|
|
25
|
-
const config = (await loadEffectiveConfig(root, "project")).config;
|
|
26
|
-
const saved = await readWorktreeRecord(join(root, ".worktrees", sessionWorktreeName(sessionId)));
|
|
27
|
-
const contextRoot = saved?.mainRoot === root && saved.sessionId === sessionId ? saved.path : root;
|
|
28
|
-
const contextConfig = contextRoot === root ? config : (await loadEffectiveConfig(contextRoot, "project")).config;
|
|
29
|
-
const tasks = new TaskService(new FileTaskStore(join(contextRoot, ".agent-ops/tasks/state.json"), contextRoot), { completion: { root: contextRoot, gitRunner: gitRunner(contextRoot), loadConfig: async () => contextConfig } });
|
|
30
|
-
const attached = await tasks.status({ sessionId }).catch(() => null);
|
|
31
|
-
if (attached?.status === "active" && attached.task.debug !== undefined)
|
|
32
|
-
await tasks.debugService().foregroundStart(attached.task.id, sessionId);
|
|
33
|
-
}
|
|
34
|
-
export async function routeDebugPrompt(root, input, host = "claude") {
|
|
35
|
-
if (typeof input !== "object" || input === null || Array.isArray(input))
|
|
36
|
-
return null;
|
|
37
|
-
const fields = input;
|
|
38
|
-
if (typeof fields.prompt !== "string")
|
|
39
|
-
return null;
|
|
40
|
-
const continuation = /^(?:繼續|继续|continue)[。.!!]?$/iu.test(fields.prompt.trim());
|
|
41
|
-
const debugIssue = isDebugIssue(fields.prompt);
|
|
42
|
-
const config = (await loadEffectiveConfig(root, "project")).config;
|
|
43
|
-
if (config.features.debug?.enabled !== true)
|
|
44
|
-
return null;
|
|
45
|
-
if (evaluateGuardrail({ kind: "content", content: fields.prompt, scope: root }).action === "block")
|
|
46
|
-
return null;
|
|
47
|
-
const session = nativeSessionId(fields);
|
|
48
|
-
if (session === undefined) {
|
|
49
|
-
if (!debugIssue && !continuation)
|
|
50
|
-
return null;
|
|
51
|
-
return "DEBUG intake unsupported: this hook has no reliable native session identity. Use task create --debug --goal with the original issue in the native conversation.";
|
|
52
|
-
}
|
|
53
|
-
const saved = await readWorktreeRecord(join(root, ".worktrees", sessionWorktreeName(session)));
|
|
54
|
-
const contextRoot = saved?.mainRoot === root && saved.sessionId === session ? saved.path : root;
|
|
55
|
-
const contextConfig = contextRoot === root ? config : (await loadEffectiveConfig(contextRoot, "project")).config;
|
|
56
|
-
const original = new TaskService(new FileTaskStore(join(contextRoot, ".agent-ops/tasks/state.json"), contextRoot), { completion: { root: contextRoot, gitRunner: gitRunner(contextRoot), loadConfig: async () => contextConfig } });
|
|
57
|
-
const attached = await original.status({ sessionId: session }).catch(() => null);
|
|
58
|
-
if (attached?.status === "active" && attached.task.debug !== undefined) {
|
|
59
|
-
await original.debugService().syncNativeControl(attached.task.id, session);
|
|
60
|
-
const control = (await original.debugService().root(attached.task.id)).debug.control;
|
|
61
|
-
if (control?.paused === true)
|
|
62
|
-
return `DEBUG original issue ${attached.task.id} is paused by the human. Preserve its task, evidence and remaining budget; stop after saving progress. Only a native human continue event resumes it.`;
|
|
63
|
-
if (continuation) {
|
|
64
|
-
let message = `DEBUG continue preserved original issue ${attached.task.id}: native user event did not extend time; other limits remain authoritative.`;
|
|
65
|
-
try {
|
|
66
|
-
await original.debugService().extend(attached.task.id, session, "latest");
|
|
67
|
-
message = `DEBUG original issue ${attached.task.id}: native human time grant applied once; other limits remain authoritative.`;
|
|
68
|
-
}
|
|
69
|
-
catch (error) {
|
|
70
|
-
message = `DEBUG continue preserved original issue ${attached.task.id}: ${error instanceof Error ? error.message : "Native user approval unavailable"}`;
|
|
71
|
-
}
|
|
72
|
-
if (foregroundClockSupported(host)) {
|
|
73
|
-
try {
|
|
74
|
-
await original.debugService().foregroundStart(attached.task.id, session);
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
message += ` Foreground clock unavailable: ${error instanceof Error ? error.message : "host lifecycle is unsupported"}`;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
else
|
|
81
|
-
message += " Foreground clock unsupported on this host; preserve the issue and use a host with a trustworthy end-of-turn signal.";
|
|
82
|
-
if (control?.mode === "diagnose")
|
|
83
|
-
message += " Diagnosis-only scope remains active; continue discriminating evidence without product repairs until a proven diagnosis or runtime blocker. Only a native human resume repair event restores the full loop.";
|
|
84
|
-
return message;
|
|
85
|
-
}
|
|
86
|
-
if (foregroundClockSupported(host)) {
|
|
87
|
-
try {
|
|
88
|
-
await original.debugService().foregroundStart(attached.task.id, session);
|
|
89
|
-
}
|
|
90
|
-
catch (error) {
|
|
91
|
-
return `DEBUG original issue ${attached.task.id}: foreground clock unavailable: ${error instanceof Error ? error.message : "host lifecycle is unsupported"}`;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
if (control?.mode === "diagnose")
|
|
95
|
-
return `DEBUG original issue ${attached.task.id}: diagnosis only. Continue reproduction and hypothesis evidence until a supported diagnosis or a runtime blocker. Do not repair product behavior. A failure to reproduce is not a stop condition; a human resume repair event restores the full loop.`;
|
|
96
|
-
if (!debugIssue)
|
|
97
|
-
return null;
|
|
98
|
-
return `Continue original DEBUG issue ${attached.task.id}; retain its existing budget, evidence and hypotheses.`;
|
|
99
|
-
}
|
|
100
|
-
if (!debugIssue && !continuation)
|
|
101
|
-
return null;
|
|
102
|
-
if (continuation) {
|
|
103
|
-
if (attached?.status !== "active" || attached.task.debug === undefined)
|
|
104
|
-
return null;
|
|
105
|
-
try {
|
|
106
|
-
await original.debugService().extend(attached.task.id, session, "latest");
|
|
107
|
-
return `DEBUG original issue ${attached.task.id}: native human time grant applied once; other limits remain authoritative.`;
|
|
108
|
-
}
|
|
109
|
-
catch (error) {
|
|
110
|
-
return `DEBUG continue preserved original issue ${attached.task.id}: ${error instanceof Error ? error.message : "Native user approval unavailable"}`;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
if (attached?.status === "active")
|
|
114
|
-
return attached.task.debug === undefined ? null : `Continue original DEBUG issue ${attached.task.id}; retain its existing budget, evidence and hypotheses.`;
|
|
115
|
-
// Another session's worktree may already hold this issue; reusing it keeps
|
|
116
|
-
// its consumed budget and rounds instead of starting a fresh task.
|
|
117
|
-
const holders = await listWorktrees(worktreeDependencies(), root).catch(() => []);
|
|
118
|
-
for (const { record: held } of holders) {
|
|
119
|
-
if (held.sessionId === session)
|
|
120
|
-
continue;
|
|
121
|
-
const heldConfig = (await loadEffectiveConfig(held.path, "project").catch(() => null))?.config;
|
|
122
|
-
if (heldConfig === undefined)
|
|
123
|
-
continue;
|
|
124
|
-
const issue = await new TaskService(new FileTaskStore(join(held.path, ".agent-ops/tasks/state.json"), held.path), { completion: { root: held.path, gitRunner: gitRunner(held.path), loadConfig: async () => heldConfig } }).activeDebugRoot(fields.prompt).catch(() => null);
|
|
125
|
-
if (issue === null)
|
|
126
|
-
continue;
|
|
127
|
-
try {
|
|
128
|
-
await resumeWorktree(worktreeDependencies(), { cwd: root, name: held.name, sessionId: session });
|
|
129
|
-
}
|
|
130
|
-
catch (error) {
|
|
131
|
-
return `DEBUG original issue ${issue.task.id} is still active in ${held.path}; a new task would reset its budget. ${error instanceof Error ? error.message : "Resume needs confirmation."} After the user confirms, run agent-ops worktree resume ${held.name} --session ${session}.`;
|
|
132
|
-
}
|
|
133
|
-
return `Continue original DEBUG issue ${issue.task.id} in ${held.path}; its budget, rounds and evidence are preserved.`;
|
|
134
|
-
}
|
|
135
|
-
await original.assertDebugBaseline();
|
|
136
|
-
const record = config.worktree?.mode === "auto" && await readWorktreeRecord(root) === null ?
|
|
137
|
-
await ensureSessionWorktree(worktreeDependencies(), { cwd: root, sessionId: session }) : null;
|
|
138
|
-
const path = record?.path ?? root;
|
|
139
|
-
const effective = (await loadEffectiveConfig(path, "project")).config;
|
|
140
|
-
const tasks = new TaskService(new FileTaskStore(join(path, ".agent-ops/tasks/state.json"), path), { completion: { root: path, gitRunner: gitRunner(path), loadConfig: async () => effective } });
|
|
141
|
-
const existing = await tasks.status({ sessionId: session }).catch(() => null);
|
|
142
|
-
if (existing?.status === "active")
|
|
143
|
-
return existing.task.debug === undefined ? null : `Continue original DEBUG issue ${existing.task.id}.`;
|
|
144
|
-
const held = await tasks.activeDebugRoot(fields.prompt);
|
|
145
|
-
if (held !== null) {
|
|
146
|
-
await tasks.attach(session, held.task.id);
|
|
147
|
-
let message = `Continue original DEBUG issue ${held.task.id}; its budget, rounds and evidence are preserved.`;
|
|
148
|
-
if (foregroundClockSupported(host)) {
|
|
149
|
-
try {
|
|
150
|
-
await tasks.debugService().foregroundStart(held.task.id, session);
|
|
151
|
-
}
|
|
152
|
-
catch (error) {
|
|
153
|
-
message += ` Foreground clock unavailable: ${error instanceof Error ? error.message : "host lifecycle is unsupported"}`;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return message;
|
|
157
|
-
}
|
|
158
|
-
const ids = effective.verification.commands.filter(c => c.required).map(c => c.id);
|
|
159
|
-
const baseline = (await gitRunner(path).run(["rev-parse", "--verify", "HEAD"])).stdout.toString().trim();
|
|
160
|
-
const criteria = ["issue-regression", "related-checks"].map(id => ({ id, description: id === "issue-regression" ? "Reproduce and fix the original reported issue with relevant regression evidence." : "Pass necessary related checks and two fresh independent reviews.", verifierIds: ids,
|
|
161
|
-
acceptance: { mode: "review-only", baselineCommit: baseline, bindings: [], reviewOnlyReason: "Provisional investigation intake only. Identify and revise to executable original-issue regression before debug success; this criterion cannot prove reproduction." } }));
|
|
162
|
-
const created = await tasks.create({ title: fields.prompt.split("\n")[0].slice(0, 160), goal: fields.prompt, intent: "Investigate and fix the original issue through authoritative task debug evidence.", debug: { facts: ["User-reported symptom is unverified."], unknowns: ["Baseline reproduction", "Root cause", "Relevant regression and related checks"] }, criteria, sessionId: session, policyConfigHash: calculateConfigHash(effective), requireSurface: true });
|
|
163
|
-
let clockNote = "";
|
|
164
|
-
if (foregroundClockSupported(host)) {
|
|
165
|
-
try {
|
|
166
|
-
await tasks.debugService().foregroundStart(created.task.id, session);
|
|
167
|
-
}
|
|
168
|
-
catch (error) {
|
|
169
|
-
return `DEBUG issue ${created.task.id} created in ${path}, but its foreground clock is unavailable: ${error instanceof Error ? error.message : "host lifecycle is unsupported"}. Preserve the issue and do not claim timed completion.`;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
else
|
|
173
|
-
clockNote = " Native foreground timing is unsupported on this host; controlled operations remain evidence-timed and do not claim a complete active-time budget.";
|
|
174
|
-
return `DEBUG issue ${created.task.id} created in ${path}. Preserve the original goal. Investigate, record falsifiable hypotheses, revise provisional criteria to relevant behavioral regression, add the surface audit (task revise --surface-file) before the first repair, repair, declare the origin (task debug --debug-action origin) once a hypothesis is confirmed, verify, obtain two fresh independent reviews, then finish. A clean HEAD baseline is fixed automatically; do not ask permission for it or offer diagnosis-only as a fallback. Failure to reproduce alone is not a stop condition: continue discriminating checks within the budget. Defaults: 60 active minutes / 5 material repair candidates. No user-authored debug plan is required.${clockNote}`;
|
|
175
|
-
}
|
|
176
|
-
export async function interruptDebugSession(root, sessionId, nativeInput) {
|
|
177
|
-
const saved = await readWorktreeRecord(join(root, ".worktrees", sessionWorktreeName(sessionId)));
|
|
178
|
-
const path = saved?.mainRoot === root && saved.sessionId === sessionId ? saved.path : root;
|
|
179
|
-
const tasks = new TaskService(new FileTaskStore(join(path, ".agent-ops/tasks/state.json"), path), { completion: { root: path, gitRunner: gitRunner(path), loadConfig: async () => (await loadEffectiveConfig(path, "project")).config } });
|
|
180
|
-
const attached = await tasks.status({ sessionId }).catch(error => {
|
|
181
|
-
if (error instanceof AgentOpsError && error.code === "TASK_SESSION_UNATTACHED")
|
|
182
|
-
return null;
|
|
183
|
-
throw error;
|
|
184
|
-
});
|
|
185
|
-
if (attached?.status === "active" && attached.task.debug !== undefined)
|
|
186
|
-
await tasks.debugService().interrupt(attached.task.id, sessionId, nativeInput);
|
|
187
|
-
}
|
|
188
|
-
/** Close only the task attached to a native host's verified session. */
|
|
189
|
-
export async function closeDebugSession(root, sessionId, host = "claude") {
|
|
190
|
-
if (!foregroundClockSupported(host))
|
|
191
|
-
return;
|
|
192
|
-
if (sessionId.length === 0)
|
|
193
|
-
return;
|
|
194
|
-
const saved = await readWorktreeRecord(join(root, ".worktrees", sessionWorktreeName(sessionId)));
|
|
195
|
-
const contextRoot = saved?.mainRoot === root && saved.sessionId === sessionId ? saved.path : root;
|
|
196
|
-
const tasks = new TaskService(new FileTaskStore(join(contextRoot, ".agent-ops/tasks/state.json"), contextRoot), { completion: { root: contextRoot, gitRunner: gitRunner(contextRoot), loadConfig: async () => (await loadEffectiveConfig(contextRoot, "project")).config } });
|
|
197
|
-
const attached = await tasks.status({ sessionId }).catch(() => null);
|
|
198
|
-
if (attached?.status === "active" && attached.task.debug !== undefined)
|
|
199
|
-
await tasks.debugService().foregroundStop(attached.task.id, sessionId);
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* Without the completion gate, an active DEBUG issue still cannot end a turn
|
|
203
|
-
* unless a current runtime stop checkpoint exists. Sessions without one pass.
|
|
204
|
-
*/
|
|
205
|
-
export function debugStopGate(root, rootConfig) {
|
|
206
|
-
return { handle: async (event) => {
|
|
207
|
-
if (event.event !== "stop" || event.sessionId === undefined || event.sessionId.length === 0 ||
|
|
208
|
-
event.terminationReason !== "model_stop" || event.fullyIdle !== true)
|
|
209
|
-
return null;
|
|
210
|
-
const sessionId = event.sessionId;
|
|
211
|
-
const evaluate = async (attempt) => {
|
|
212
|
-
const saved = await readWorktreeRecord(join(root, ".worktrees", sessionWorktreeName(sessionId))).catch(() => null);
|
|
213
|
-
const contextRoot = saved?.mainRoot === root && saved.sessionId === sessionId ? saved.path : root;
|
|
214
|
-
const config = contextRoot === root && attempt === 0 ? rootConfig : (await loadEffectiveConfig(contextRoot, "project")).config;
|
|
215
|
-
const runner = gitRunner(contextRoot);
|
|
216
|
-
const tasks = new TaskService(new FileTaskStore(join(contextRoot, ".agent-ops/tasks/state.json"), contextRoot), { completion: { root: contextRoot, gitRunner: runner, loadConfig: async () => config } });
|
|
217
|
-
const attached = await tasks.status({ sessionId }).catch(error => {
|
|
218
|
-
if (error instanceof AgentOpsError && error.code === "TASK_SESSION_UNATTACHED")
|
|
219
|
-
return null;
|
|
220
|
-
throw error;
|
|
221
|
-
});
|
|
222
|
-
if (attached?.status !== "active" || attached.task.debug === undefined)
|
|
223
|
-
return null;
|
|
224
|
-
await tasks.debugService().prepareStop(attached.task.id, sessionId);
|
|
225
|
-
const surface = await collectChangeSurface(runner);
|
|
226
|
-
const fingerprint = await calculateSourceFingerprint(contextRoot, { mode: "worktree", changedFiles: surface.paths }, runner);
|
|
227
|
-
return await tasks.debugService().validCheckpoint(attached.task.id, sessionId, fingerprint)
|
|
228
|
-
? { action: "continue", status: "UNKNOWN", code: "COMPLETION_GATE_TASK_DEBUG_STOPPED", remedy: "Original issue remains active with a runtime-bound non-success checkpoint." }
|
|
229
|
-
: { action: "block", status: "FAIL", code: "COMPLETION_GATE_TASK_DEBUG_ACTIVE", remedy: "Continue the original DEBUG investigation, repair, verification and review loop. Failure to reproduce and an arbitrary checkpoint do not permit stopping. Preserve the task and budgets; record a current runtime stop checkpoint only for a proven blocker or exhausted limits." };
|
|
230
|
-
};
|
|
231
|
-
// Reload the task/config once for a transient gate fault; never rewrite bad evidence.
|
|
232
|
-
for (let attempt = 0; attempt < 2; attempt++) {
|
|
233
|
-
try {
|
|
234
|
-
return await evaluate(attempt);
|
|
235
|
-
}
|
|
236
|
-
catch (error) {
|
|
237
|
-
if (attempt === 0)
|
|
238
|
-
continue;
|
|
239
|
-
const code = error instanceof AgentOpsError ? error.code : "IO_ERROR";
|
|
240
|
-
return { action: "continue", status: "UNKNOWN", code: "TASK_DEBUG_GATE_UNAVAILABLE", remedy: `DEBUG remains incomplete: gate ${code} failed after one targeted reload. Preserve the original task, evidence and budgets; run agent-ops doctor and restore readable task/config evidence before resuming. No completion is attested.` };
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
return null;
|
|
244
|
-
} };
|
|
245
|
-
}
|