immune-brain 3.6.8 → 4.0.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 +11 -4
- package/README.zh-CN.md +10 -3
- package/package.json +3 -2
- package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
- package/plugins/immune-brain/.pi-extension/imm-canary-enroll.ts +18 -2
- package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +76 -121
- package/plugins/immune-brain/.pi-extension/imm-unattended-batch.ts +106 -600
- package/plugins/immune-brain/.pi-extension/pi-canary-assurance-progression.ts +1 -0
- package/plugins/immune-brain/.pi-extension/pi-canary-verification.ts +3 -3
- package/plugins/immune-brain/.pi-extension/runtime-stub.ts +17 -43
- package/plugins/immune-brain/dist/claude/mcp-server.mjs +7581 -5042
- package/plugins/immune-brain/dist/docs/reference/planning-artifact-retention.md +11 -12
- package/plugins/immune-brain/dist/docs/reference/subagent-dispatch-protocol.md +1 -1
- package/plugins/immune-brain/dist/imm-loop.md +27 -25
- package/plugins/immune-brain/dist/imm-planner.md +53 -32
- package/plugins/immune-brain/dist/imm-review-retro.md +123 -0
- package/plugins/immune-brain/dist/registry.yaml +9 -0
- package/plugins/immune-brain/dist/role-prompts/code-review.md +3 -1
- package/plugins/immune-brain/dist/role-prompts/executor.md +4 -4
- package/plugins/immune-brain/runtime/assurance/coordinator.ts +183 -40
- package/plugins/immune-brain/runtime/assurance/delivery_workspace.ts +240 -0
- package/plugins/immune-brain/runtime/assurance/qa.ts +132 -58
- package/plugins/immune-brain/runtime/assurance/review_evidence.ts +15 -7
- package/plugins/immune-brain/runtime/assurance/verification.ts +246 -206
- package/plugins/immune-brain/runtime/authorization_operation.ts +20 -0
- package/plugins/immune-brain/runtime/claude/kernel_ports.ts +288 -721
- package/plugins/immune-brain/runtime/commands/kernel.ts +158 -67
- package/plugins/immune-brain/runtime/github_issue_tracker.ts +254 -29
- package/plugins/immune-brain/runtime/kernel/actor_identity.ts +33 -0
- package/plugins/immune-brain/runtime/kernel/application.ts +22 -6
- package/plugins/immune-brain/runtime/kernel/assurance_projection.ts +94 -5
- package/plugins/immune-brain/runtime/kernel/authority_port.ts +27 -6
- package/plugins/immune-brain/runtime/kernel/backend_claim.ts +43 -16
- package/plugins/immune-brain/runtime/kernel/batch_authority.ts +10 -6
- package/plugins/immune-brain/runtime/kernel/canary_application.ts +50 -63
- package/plugins/immune-brain/runtime/kernel/canary_eligibility.ts +13 -4
- package/plugins/immune-brain/runtime/kernel/completion.ts +5 -14
- package/plugins/immune-brain/runtime/kernel/enrollment.ts +124 -34
- package/plugins/immune-brain/runtime/kernel/enrollment_authority.ts +13 -5
- package/plugins/immune-brain/runtime/kernel/index.ts +3 -1
- package/plugins/immune-brain/runtime/kernel/intent.ts +7 -11
- package/plugins/immune-brain/runtime/kernel/legacy_audit.ts +4 -1
- package/plugins/immune-brain/runtime/kernel/legacy_task_record.ts +323 -0
- package/plugins/immune-brain/runtime/kernel/pi_canary_prepare.ts +10 -1
- package/plugins/immune-brain/runtime/kernel/reducer.ts +32 -31
- package/plugins/immune-brain/runtime/kernel/run_identity.ts +121 -0
- package/plugins/immune-brain/runtime/kernel/spec_binding.ts +100 -0
- package/plugins/immune-brain/runtime/kernel/sqlite_migration.ts +950 -0
- package/plugins/immune-brain/runtime/kernel/sqlite_store.ts +1193 -0
- package/plugins/immune-brain/runtime/kernel/storage.ts +1254 -1206
- package/plugins/immune-brain/runtime/kernel/storage_layout_migration.ts +129 -755
- package/plugins/immune-brain/runtime/kernel/storage_paths.ts +419 -46
- package/plugins/immune-brain/runtime/kernel/types.ts +12 -43
- package/plugins/immune-brain/runtime/kernel/validation.ts +60 -274
- package/plugins/immune-brain/runtime/managed_task_routing_policy.ts +0 -1
- package/plugins/immune-brain/runtime/plan_core.ts +27 -65
- package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
- package/plugins/immune-brain/runtime/prompts/code-review.md +3 -1
- package/plugins/immune-brain/runtime/prompts/executor.md +4 -4
- package/plugins/immune-brain/runtime/staged_intent.ts +58 -0
- package/plugins/immune-brain/runtime/unattended/batch_git.ts +37 -7
- package/plugins/immune-brain/runtime/unattended/batch_plan.ts +42 -2
- package/plugins/immune-brain/runtime/unattended/batch_preflight.ts +771 -0
- package/plugins/immune-brain/runtime/unattended/batch_reasons.ts +189 -0
- package/plugins/immune-brain/runtime/unattended/batch_runner.ts +35 -0
- package/plugins/immune-brain/runtime/unattended/confirmation_deadline.ts +33 -0
- package/plugins/immune-brain/runtime/unattended/types.ts +14 -1
- package/plugins/immune-brain/runtime/v4_runtime.ts +19 -23
- package/plugins/immune-brain/runtime/verification_descriptor.ts +92 -136
- package/plugins/immune-brain/runtime/workspace_scope.ts +98 -13
- package/plugins/immune-brain/skills/imm-planner/SKILL.md +3 -3
- package/plugins/immune-brain/skills/imm-review-retro/SKILL.md +23 -0
- package/plugins/immune-brain/skills/imm-review-retro/scripts/review_retro.ts +355 -0
- package/plugins/immune-brain/skills/registry.yaml +9 -0
- package/plugins/immune-brain/bin/imm-retire-stale-wrapper +0 -4
- package/plugins/immune-brain/bin/imm-retired +0 -4
- package/plugins/immune-brain/runtime/authority_commit_receipts.ts +0 -716
- package/plugins/immune-brain/runtime/kernel/automatic_observations.ts +0 -451
- package/plugins/immune-brain/runtime/kernel/legacy.ts +0 -299
- package/plugins/immune-brain/runtime/kernel/observation.ts +0 -397
- package/plugins/immune-brain/runtime/kernel/readiness.ts +0 -282
- package/plugins/immune-brain/runtime/kernel/readiness_evidence.ts +0 -132
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ Pi and Claude Code are the supported hosts. Undeclared adapters remain unsupport
|
|
|
25
25
|
- [Installation](#installation)
|
|
26
26
|
- [Quick Start](#quick-start)
|
|
27
27
|
- [How to Use](#how-to-use)
|
|
28
|
-
- [The
|
|
28
|
+
- [The 7 Skills](#the-7-skills)
|
|
29
29
|
- [Lifecycle](#lifecycle)
|
|
30
30
|
- [Unattended Batch Runs](#unattended-batch-runs)
|
|
31
31
|
- [Configuration](#configuration)
|
|
@@ -119,6 +119,7 @@ Immune-Brain provides two clean modes: **Host-native** for daily coding, and **M
|
|
|
119
119
|
| PR has review comments or failing CI | `/imm-pr-fix` on that PR | → Standalone repair: minimal scoped fix in place, no managed task created |
|
|
120
120
|
| Project docs out of date | `/imm-doc-prune` | → Read-only audit; deletes only user-approved stale docs from manifest |
|
|
121
121
|
| Agent instructions bloated | `/imm-agent-doc-maintain` | → Minimizes tracked `AGENTS.md` / `CLAUDE.md` to essential non-discoverable rules |
|
|
122
|
+
| Which model's edits keep coming back for review | `/imm-review-retro` | → Ranks models by review load and reports project usage from session logs |
|
|
122
123
|
|
|
123
124
|
> **Core Principle: Skill-Explicit Entry**
|
|
124
125
|
> - **Ordinary input stays host-native**: Natural language queries never automatically start planning or task enrollment. You choose when to turn on engineering rigor.
|
|
@@ -126,7 +127,7 @@ Immune-Brain provides two clean modes: **Host-native** for daily coding, and **M
|
|
|
126
127
|
|
|
127
128
|
---
|
|
128
129
|
|
|
129
|
-
## The
|
|
130
|
+
## The 7 Skills
|
|
130
131
|
|
|
131
132
|
| Skill | Type | When to use | What it does |
|
|
132
133
|
|---|---|---|---|
|
|
@@ -136,10 +137,11 @@ Immune-Brain provides two clean modes: **Host-native** for daily coding, and **M
|
|
|
136
137
|
| `imm-pr-fix` | Standalone | CI failed / review comments on a PR | Repairs one PR in place, no managed authority |
|
|
137
138
|
| `imm-doc-prune` | Standalone | Stale current docs | Deletes only the hash-approved manifest entries |
|
|
138
139
|
| `imm-agent-doc-maintain` | Standalone | Bloated agent instructions | Minimizes tracked AGENTS/CLAUDE/GEMINI.md to necessary context |
|
|
140
|
+
| `imm-review-retro` | Standalone | Compare models by review load | Ranks authors of reviewed code and reports project usage |
|
|
139
141
|
|
|
140
142
|
Internal roles (Executor, QA, Review, Compounder) are dispatched by `imm-loop` — you never invoke them directly.
|
|
141
143
|
|
|
142
|
-
All
|
|
144
|
+
All 7 skills are invoked explicitly. For new features, start with `imm-brainstorm` (if requirements are uncertain) or `imm-planner` (if requirements are clear), then proceed to `imm-loop` once enrolled.
|
|
143
145
|
|
|
144
146
|
### Managed Path entries (brainstorm → planner → loop)
|
|
145
147
|
|
|
@@ -187,6 +189,11 @@ The three repair/maintenance skills are host-native: they never create a managed
|
|
|
187
189
|
- **Trigger:** explicit request to minimize tracked `AGENTS.md` / `CLAUDE.md` / `GEMINI.md`.
|
|
188
190
|
- **What it does:** keeps only the necessary non-discoverable rules in agent instruction files, under the same read-only-audit + hash-bound-manifest-approval model as `imm-doc-prune`.
|
|
189
191
|
|
|
192
|
+
#### `imm-review-retro` — review load and project usage
|
|
193
|
+
|
|
194
|
+
- **Trigger:** explicit request for a cross-model review retro or project usage look-back.
|
|
195
|
+
- **What it does:** ranks models by how much review their own edits triggered, plus sessions/turns/edits/tool mix, from pi session logs. Read-only. Not a diff review.
|
|
196
|
+
|
|
190
197
|
---
|
|
191
198
|
|
|
192
199
|
## Lifecycle
|
|
@@ -265,7 +272,7 @@ See [`docs/reference/immune-brain-config.md`](docs/reference/immune-brain-config
|
|
|
265
272
|
package.json # Pi package manifest (skills + extensions)
|
|
266
273
|
plugins/immune-brain/
|
|
267
274
|
├── .pi-extension/ # Pi TUI + Kernel authority extension
|
|
268
|
-
├── skills/ #
|
|
275
|
+
├── skills/ # 7 public Skills (trigger shims)
|
|
269
276
|
├── dist/ # Built skill contracts & references
|
|
270
277
|
├── runtime/ # Bun + TypeScript runtime & Kernel
|
|
271
278
|
└── bin/ # CLI wrappers (→ runtime/v4_runtime.ts)
|
package/README.zh-CN.md
CHANGED
|
@@ -25,7 +25,7 @@ Pi 与 Claude Code 是支持的宿主。未声明的适配器仍不受支持。C
|
|
|
25
25
|
- [安装](#安装)
|
|
26
26
|
- [快速开始](#快速开始)
|
|
27
27
|
- [如何使用](#如何使用)
|
|
28
|
-
- [
|
|
28
|
+
- [7 个 Skills](#7-个-skills)
|
|
29
29
|
- [生命周期](#生命周期)
|
|
30
30
|
- [无人值守批次运行](#无人值守批次运行)
|
|
31
31
|
- [配置](#配置)
|
|
@@ -119,6 +119,7 @@ Immune-Brain 提供两种清晰的工作模式:日常轻量编码走 **Host-na
|
|
|
119
119
|
| PR 被评论 / CI 挂了 | 对该 PR 使用 `/imm-pr-fix` | → 独立修复:在当前 PR 内针对性修复,不创建新 managed 任务 |
|
|
120
120
|
| 文档过时需要清理 | `/imm-doc-prune` | → 只读审计过时文档,仅删除经哈希审批的条目 |
|
|
121
121
|
| Agent 指令文件膨胀 | `/imm-agent-doc-maintain` | → 将 tracked `AGENTS.md` / `CLAUDE.md` 压到最小必要上下文 |
|
|
122
|
+
| 想知道哪个模型的改动总被审查 | `/imm-review-retro` | → 按模型排名审查负载,并从 session logs 汇报项目使用量 |
|
|
122
123
|
|
|
123
124
|
> **核心原则:Skill 显式调用**
|
|
124
125
|
> - **普通输入保持 Host-native**:自然语言提问绝不自动绑架流程或发起 Enrollment。你完全自主决定何时开启严格工程保障。
|
|
@@ -126,7 +127,7 @@ Immune-Brain 提供两种清晰的工作模式:日常轻量编码走 **Host-na
|
|
|
126
127
|
|
|
127
128
|
---
|
|
128
129
|
|
|
129
|
-
##
|
|
130
|
+
## 7 个 Skills
|
|
130
131
|
|
|
131
132
|
| Skill | 类型 | 何时使用 | 职责 |
|
|
132
133
|
|---|---|---|---|
|
|
@@ -136,10 +137,11 @@ Immune-Brain 提供两种清晰的工作模式:日常轻量编码走 **Host-na
|
|
|
136
137
|
| `imm-pr-fix` | 独立 | PR 需修复 | 原地修复单个 PR,不触及 managed authority |
|
|
137
138
|
| `imm-doc-prune` | 独立 | 清理过时文档 | 仅删除哈希绑定的 manifest 条目 |
|
|
138
139
|
| `imm-agent-doc-maintain` | 独立 | Agent instruction 膨胀 | 将 tracked AGENTS/CLAUDE/GEMINI.md 压到最小必要上下文 |
|
|
140
|
+
| `imm-review-retro` | 独立 | 比较模型的审查负载 | 排名被审查代码的作者并汇报项目使用量 |
|
|
139
141
|
|
|
140
142
|
Executor、QA、Review、Compounder 等为 `imm-loop` 内部调度的角色,无需手动调用。
|
|
141
143
|
|
|
142
|
-
所有
|
|
144
|
+
所有 7 个 Skill 均显式调用。新需求开发时:若需求含糊先调 `imm-brainstorm`,目标清晰直接调 `imm-planner`,完成确认后调 `imm-loop` 推进闭环。
|
|
143
145
|
|
|
144
146
|
### Managed Path 入口(brainstorm → planner → loop)
|
|
145
147
|
|
|
@@ -187,6 +189,11 @@ Executor、QA、Review、Compounder 等为 `imm-loop` 内部调度的角色,
|
|
|
187
189
|
- **触发方式:** 显式要求精简版本控制下的 `AGENTS.md` / `CLAUDE.md` / `GEMINI.md`。
|
|
188
190
|
- **职责:** 遵循与 `imm-doc-prune` 相同的「只读审计 + 哈希清单审批」模式,仅保留无法直接推导的必要规则。
|
|
189
191
|
|
|
192
|
+
#### `imm-review-retro` — 审查负载与项目使用回顾
|
|
193
|
+
|
|
194
|
+
- **触发方式:** 显式要求跨模型审查复盘或项目使用量回顾。
|
|
195
|
+
- **职责:** 从 pi session logs 按模型排名被审查代码的作者,并汇报 sessions/turns/编辑量/工具分布。只读;不审查 diff。
|
|
196
|
+
|
|
190
197
|
---
|
|
191
198
|
|
|
192
199
|
## 生命周期
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "immune-brain",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Immune-Brain agent skill system",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -72,9 +72,10 @@
|
|
|
72
72
|
"plugins/immune-brain/runtime/plan_core.ts",
|
|
73
73
|
"plugins/immune-brain/runtime/commands/kernel.ts",
|
|
74
74
|
"plugins/immune-brain/runtime/kernel",
|
|
75
|
+
"plugins/immune-brain/runtime/authorization_operation.ts",
|
|
75
76
|
"plugins/immune-brain/runtime/canonical_json.ts",
|
|
77
|
+
"plugins/immune-brain/runtime/staged_intent.ts",
|
|
76
78
|
"plugins/immune-brain/runtime/workspace_scope.ts",
|
|
77
|
-
"plugins/immune-brain/runtime/authority_commit_receipts.ts",
|
|
78
79
|
"plugins/immune-brain/runtime/managed_task_routing_policy.ts",
|
|
79
80
|
"plugins/immune-brain/runtime/github_issue_tracker.ts",
|
|
80
81
|
"plugins/immune-brain/runtime/verification_descriptor.ts",
|
|
@@ -14,6 +14,7 @@ import { resolve } from "node:path";
|
|
|
14
14
|
// can resolve them at runtime), and the stub forwards to the real Kernel
|
|
15
15
|
// modules via dynamic import.
|
|
16
16
|
import {
|
|
17
|
+
LITERAL_USER_ACTOR_ID,
|
|
17
18
|
createEnrollmentAuthorityRegistry,
|
|
18
19
|
preparePiCanary,
|
|
19
20
|
revalidatePiCanary,
|
|
@@ -408,7 +409,7 @@ async function executeForegroundEnrollment(
|
|
|
408
409
|
`Terminal task ${authority.owner_task_id} retains an exactly proven stale backend claim`,
|
|
409
410
|
`invoke imm_kernel_canary repair_authority_state for ${authority.owner_task_id}`,
|
|
410
411
|
);
|
|
411
|
-
if (authority.state === "authority_conflict"
|
|
412
|
+
if (authority.state === "authority_conflict")
|
|
412
413
|
return terminal(
|
|
413
414
|
action,
|
|
414
415
|
taskId,
|
|
@@ -417,6 +418,21 @@ async function executeForegroundEnrollment(
|
|
|
417
418
|
authority.diagnostic ?? `Kernel authority state is ${authority.state}`,
|
|
418
419
|
"inspect authority state; do not retry enrollment",
|
|
419
420
|
);
|
|
421
|
+
// terminal_owner with a local run is this worktree's settled task and
|
|
422
|
+
// forbids re-enrollment. terminal_owner *without* one is historical audit
|
|
423
|
+
// evidence (a fresh clone, or another worktree's run reaching here through
|
|
424
|
+
// Git): evidence is not local authority, and this worktree may enroll the
|
|
425
|
+
// task for the first time. The Kernel's own precondition check enforces
|
|
426
|
+
// the same rule against the local run.
|
|
427
|
+
if (authority.state === "terminal_owner" && authority.owner_run_id !== null)
|
|
428
|
+
return terminal(
|
|
429
|
+
action,
|
|
430
|
+
taskId,
|
|
431
|
+
"authority_conflict",
|
|
432
|
+
stage,
|
|
433
|
+
`task ${taskId} is terminal in this worktree`,
|
|
434
|
+
"inspect authority state; do not retry enrollment",
|
|
435
|
+
);
|
|
420
436
|
const preparation = await preparePiCanary(root, { task_id: taskId, now });
|
|
421
437
|
signal.throwIfAborted();
|
|
422
438
|
if (!preparation.intent)
|
|
@@ -523,7 +539,7 @@ async function executeForegroundEnrollment(
|
|
|
523
539
|
intent_revision: preparation.intent.revision,
|
|
524
540
|
intent_content_hash: preparation.intent.content_hash,
|
|
525
541
|
preparation_digest: preparation.digest,
|
|
526
|
-
actor_id:
|
|
542
|
+
actor_id: LITERAL_USER_ACTOR_ID,
|
|
527
543
|
confirmation_ref: `pi-confirm-${createHash("sha256").update(`${taskId}\0${now}\0${nonce}`).digest("hex").slice(0, 16)}`,
|
|
528
544
|
expires_at: new Date(Date.now() + 10 * 60 * 1000).toISOString(),
|
|
529
545
|
nonce,
|
|
@@ -23,11 +23,6 @@ import { Type } from "typebox";
|
|
|
23
23
|
import { PLUGIN_VERSION } from "../runtime/plugin_version";
|
|
24
24
|
import {
|
|
25
25
|
parseVerificationDescriptor,
|
|
26
|
-
canonicalDescriptorBytes,
|
|
27
|
-
resolveBunRunner,
|
|
28
|
-
assertRunnerCompatible,
|
|
29
|
-
findingsDigest,
|
|
30
|
-
type FrozenRunner,
|
|
31
26
|
type VerificationDescriptor,
|
|
32
27
|
} from "./pi-canary-verification";
|
|
33
28
|
import {
|
|
@@ -43,9 +38,9 @@ import {
|
|
|
43
38
|
import type { InvocationToken } from "./pi-canary-invocations";
|
|
44
39
|
import { runDeterministicQa } from "../runtime/assurance/qa";
|
|
45
40
|
import {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
} from "
|
|
41
|
+
captureStagedIntent,
|
|
42
|
+
restoreStagedIntent as restoreStagedIntentShared,
|
|
43
|
+
} from "../runtime/staged_intent";
|
|
49
44
|
import {
|
|
50
45
|
renderCanaryCall,
|
|
51
46
|
renderCanaryResult,
|
|
@@ -71,13 +66,13 @@ import {
|
|
|
71
66
|
} from "./pi-canary-interaction";
|
|
72
67
|
import { isToolFailureState, throwToolFailure, type ToolFailureV1 } from "./pi-canary-tool-failure";
|
|
73
68
|
import { taskDiffIdentity, taskRevisionIdentity, captureGitTaskSnapshot } from "../runtime/workspace_scope";
|
|
74
|
-
import { reviewReworkFindings } from "../runtime/assurance/coordinator";
|
|
69
|
+
import { reviewAdvisoryRecords, reviewReworkFindings } from "../runtime/assurance/coordinator";
|
|
75
70
|
import {
|
|
76
71
|
AssuranceProgression,
|
|
77
72
|
buildReviewPrompt,
|
|
78
73
|
classifyReviewWorkload,
|
|
79
74
|
deriveQaJobTimeoutMs,
|
|
80
|
-
|
|
75
|
+
projectTerminalTrackerState,
|
|
81
76
|
parseAssuranceVerdict,
|
|
82
77
|
snapshotDigest,
|
|
83
78
|
QA_JOB_TIMEOUT_SECONDS,
|
|
@@ -85,12 +80,9 @@ import {
|
|
|
85
80
|
REVIEW_PREPARATION_TIMEOUT_MS,
|
|
86
81
|
REVIEW_TIMING_PROFILES,
|
|
87
82
|
REVIEW_VERDICT_VALIDATION_TIMEOUT_MS,
|
|
88
|
-
type AssuranceAdvanceResult,
|
|
89
83
|
type AssuranceProgressionPorts,
|
|
90
|
-
type AssuranceSubmitReviewResult,
|
|
91
84
|
type AssuranceVerdict,
|
|
92
85
|
type HostContext,
|
|
93
|
-
type QaVerificationProgress,
|
|
94
86
|
type SnapshotDescriptor,
|
|
95
87
|
} from "./pi-canary-assurance-progression";
|
|
96
88
|
|
|
@@ -109,27 +101,25 @@ import {
|
|
|
109
101
|
reconcileKernelAuthority,
|
|
110
102
|
repairKernelAuthority,
|
|
111
103
|
readTaskRecord,
|
|
112
|
-
|
|
104
|
+
withKernelStoreLockForTask,
|
|
113
105
|
inspectStorageLayout,
|
|
114
106
|
migrateLegacyLayout,
|
|
115
107
|
readTaskIntent,
|
|
116
108
|
parseTaskIntentV1,
|
|
117
109
|
canonicalIntentHash,
|
|
118
110
|
projectAssurance,
|
|
119
|
-
deriveAssuranceAuthorization,
|
|
120
111
|
findingsDigestV2,
|
|
121
112
|
capabilityActionFor,
|
|
122
113
|
digestOfAction,
|
|
123
|
-
type AssuranceAuthorizationReadiness,
|
|
124
114
|
type AssuranceProjectionResult,
|
|
125
115
|
type TaskRecordRead,
|
|
126
116
|
type CanaryApplication,
|
|
127
117
|
type MutationAuthorityRegistry,
|
|
128
118
|
type CapabilityBindingV2,
|
|
119
|
+
LITERAL_USER_ACTOR_ID,
|
|
129
120
|
} from "./runtime-stub";
|
|
130
121
|
import { invocationRegistry } from "./pi-canary-assurance-progression";
|
|
131
122
|
|
|
132
|
-
const TASK_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
133
123
|
const LOOP_OWNERS = ["plan", "kernel", "brainstorm", "planner", "loop"] as const;
|
|
134
124
|
const LOOP_TARGETS = [
|
|
135
125
|
"step",
|
|
@@ -186,6 +176,7 @@ export interface SnapshotDescriptorInput {
|
|
|
186
176
|
root: string;
|
|
187
177
|
task_id: string;
|
|
188
178
|
role: AssuranceRole;
|
|
179
|
+
run_id?: string | null;
|
|
189
180
|
record_revision: string;
|
|
190
181
|
workspace_revision: string;
|
|
191
182
|
intent_revision: number;
|
|
@@ -214,6 +205,7 @@ export function buildSnapshot(input: SnapshotDescriptorInput): SnapshotDescripto
|
|
|
214
205
|
return {
|
|
215
206
|
contract: "assurance_kernel/assurance_snapshot/v2",
|
|
216
207
|
task_id: input.task_id,
|
|
208
|
+
run_id: input.run_id ?? null,
|
|
217
209
|
role: input.role,
|
|
218
210
|
record_revision: input.record_revision,
|
|
219
211
|
workspace_revision: input.workspace_revision,
|
|
@@ -282,12 +274,11 @@ export function createPiAssuranceProgressionPorts(
|
|
|
282
274
|
projectTask: (root, taskId) => projectAssuranceState(root, taskId),
|
|
283
275
|
readTaskRecord: (root, taskId) => readTaskRecord(root, taskId),
|
|
284
276
|
readTaskIntent: (root, taskId) => readTaskIntent(root, taskId),
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
(dependencies.buildAssurance ?? buildAssuranceSnapshot)(root, taskId, role, projection, runner),
|
|
277
|
+
buildAssurance: (root, taskId, role, projection) =>
|
|
278
|
+
(dependencies.buildAssurance ?? buildAssuranceSnapshot)(root, taskId, role, projection),
|
|
288
279
|
ensureReviewRevision: (root, taskId, projection) => ensureTaskReviewRevision(root, taskId, projection),
|
|
289
|
-
runQa: (snapshot, descriptors,
|
|
290
|
-
(dependencies.runQa ?? runDeterministicQa)(snapshot, descriptors,
|
|
280
|
+
runQa: (snapshot, descriptors, options) =>
|
|
281
|
+
(dependencies.runQa ?? runDeterministicQa)(snapshot, descriptors, options),
|
|
291
282
|
writeReviewEvidence: (input) =>
|
|
292
283
|
(dependencies.writeReviewEvidence ?? writeNativeReviewEvidence)(input),
|
|
293
284
|
applyVerdict: (ctx, input) =>
|
|
@@ -491,7 +482,9 @@ export default function (
|
|
|
491
482
|
if (ctx.mode !== "tui")
|
|
492
483
|
return failCanaryTool(taskId, action.op, "blocked", "tui_required", "Kernel mutation is TUI-only", "invoke the TUI Tool");
|
|
493
484
|
try {
|
|
494
|
-
|
|
485
|
+
// Recovery also retires a leftover derived claim/owner file whose
|
|
486
|
+
// task the store already holds: that file is inert, not authority.
|
|
487
|
+
await withKernelStoreLockForTask(ctx.cwd, taskId, () => undefined);
|
|
495
488
|
} catch (error) {
|
|
496
489
|
return failCanaryTool(taskId, action.op, "blocked", "layout_recovery_failed", `Kernel transaction recovery failed: ${error instanceof Error ? error.message : String(error)}`, "resolve the pending marker and retry");
|
|
497
490
|
}
|
|
@@ -510,6 +503,25 @@ export default function (
|
|
|
510
503
|
if (action.op === "repair_authority_state") {
|
|
511
504
|
if (ctx.mode !== "tui") return failCanaryTool(taskId, action.op, "blocked", "tui_required", "imm_kernel_canary mutation is TUI-only", "invoke the TUI Tool");
|
|
512
505
|
const authority = await reconcileKernelAuthority(ctx.cwd, taskId);
|
|
506
|
+
if (authority.state === "terminal_owner" || authority.state === "unowned") {
|
|
507
|
+
// Repair is unrepresentable-by-construction removal: a leftover
|
|
508
|
+
// retired claim file is inert, and the store already holds the
|
|
509
|
+
// settled answer. Report the authority and write nothing.
|
|
510
|
+
try {
|
|
511
|
+
const repaired = await repairKernelAuthority(ctx.cwd, taskId, authority.revision);
|
|
512
|
+
const result = {
|
|
513
|
+
state: "recovered_retry",
|
|
514
|
+
operation: action.op,
|
|
515
|
+
authority: repaired,
|
|
516
|
+
result: `No stale authority claim exists for ${taskId}; the store owns authority`,
|
|
517
|
+
next_action: "retry the blocked managed request once",
|
|
518
|
+
};
|
|
519
|
+
return toolResult(JSON.stringify(result, null, 2), result);
|
|
520
|
+
} catch (error) {
|
|
521
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
522
|
+
return failCanaryTool(taskId, action.op, "blocked", "authority_repair_failed", message, "inspect the kernel store authority state and retry");
|
|
523
|
+
}
|
|
524
|
+
}
|
|
513
525
|
if (
|
|
514
526
|
authority.state !== "repairable_stale_claim" ||
|
|
515
527
|
authority.owner_task_id !== taskId
|
|
@@ -762,38 +774,11 @@ export default function (
|
|
|
762
774
|
const priorIntent = await readTaskIntent(ctx.cwd, taskId);
|
|
763
775
|
const sidecar = nextIntent ? join(ctx.cwd, priorIntent.intent_ref.path) : undefined;
|
|
764
776
|
const priorBytes = sidecar ? readFileSync(sidecar) : undefined;
|
|
765
|
-
const
|
|
766
|
-
? execFileSync("git", ["ls-files", "--stage", "-z", "--", priorIntent.intent_ref.path], {
|
|
767
|
-
cwd: ctx.cwd,
|
|
768
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
769
|
-
})
|
|
770
|
-
: undefined;
|
|
777
|
+
const stagedSnapshot = sidecar ? captureStagedIntent(ctx.cwd, priorIntent.intent_ref.path) : undefined;
|
|
771
778
|
let stagedNextDiffHash: string | undefined;
|
|
772
779
|
const restoreStagedIntent = (): void => {
|
|
773
|
-
if (!
|
|
774
|
-
|
|
775
|
-
execFileSync("git", ["update-index", "--force-remove", "--", priorIntent.intent_ref.path], {
|
|
776
|
-
cwd: ctx.cwd,
|
|
777
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
778
|
-
});
|
|
779
|
-
if (priorIndexState.length > 0) {
|
|
780
|
-
execFileSync("git", ["update-index", "-z", "--index-info"], {
|
|
781
|
-
cwd: ctx.cwd,
|
|
782
|
-
input: priorIndexState,
|
|
783
|
-
stdio: ["pipe", "ignore", "pipe"],
|
|
784
|
-
});
|
|
785
|
-
}
|
|
786
|
-
const restoredBytes = readFileSync(sidecar);
|
|
787
|
-
if (!restoredBytes.equals(priorBytes)) {
|
|
788
|
-
throw new Error("failed to restore prior intent bytes");
|
|
789
|
-
}
|
|
790
|
-
const restoredIndex = execFileSync("git", ["ls-files", "--stage", "-z", "--", priorIntent.intent_ref.path], {
|
|
791
|
-
cwd: ctx.cwd,
|
|
792
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
793
|
-
});
|
|
794
|
-
if (!restoredIndex.equals(priorIndexState)) {
|
|
795
|
-
throw new Error("failed to restore prior intent git index entry");
|
|
796
|
-
}
|
|
780
|
+
if (!stagedSnapshot) return;
|
|
781
|
+
restoreStagedIntentShared(ctx.cwd, stagedSnapshot);
|
|
797
782
|
};
|
|
798
783
|
if (nextIntent) {
|
|
799
784
|
try {
|
|
@@ -992,12 +977,13 @@ export default function (
|
|
|
992
977
|
const capability = await mintCapability(registry, {
|
|
993
978
|
authority_kind: "user",
|
|
994
979
|
task_id: taskId,
|
|
980
|
+
run_id: projection.projection.run_id,
|
|
995
981
|
action_kind: exactOperation.op,
|
|
996
982
|
expected_record_hash: projection.projection.record_revision,
|
|
997
983
|
intent_revision: nextIntent?.revision ?? projection.projection.intent_revision,
|
|
998
984
|
intent_content_hash: nextIntentHash ?? projection.projection.intent_content_hash,
|
|
999
985
|
diff_hash: operationDiffHash,
|
|
1000
|
-
actor_id:
|
|
986
|
+
actor_id: LITERAL_USER_ACTOR_ID,
|
|
1001
987
|
...(exactOperation.op === "approve_breaking_intent_revision"
|
|
1002
988
|
? { next_intent: exactOperation.next_intent, next_intent_ref: exactOperation.next_intent_ref }
|
|
1003
989
|
: {}),
|
|
@@ -1010,7 +996,7 @@ export default function (
|
|
|
1010
996
|
const result = (await app.execute({
|
|
1011
997
|
root: ctx.cwd,
|
|
1012
998
|
task_id: taskId,
|
|
1013
|
-
operation: { ...exactOperation, capability, actor_id:
|
|
999
|
+
operation: { ...exactOperation, capability, actor_id: LITERAL_USER_ACTOR_ID } as never,
|
|
1014
1000
|
prior_intent_token: priorIntent.token,
|
|
1015
1001
|
diffProvider: (root: string, record: NonNullable<TaskRecordRead["record"]>) => diffSnapshotOf(root, record),
|
|
1016
1002
|
now,
|
|
@@ -1023,7 +1009,7 @@ export default function (
|
|
|
1023
1009
|
) stagePlanningArtifactTransition(ctx.cwd, result.record);
|
|
1024
1010
|
return { state: "applied", operation, lifecycle: result.record.lifecycle };
|
|
1025
1011
|
} catch (error) {
|
|
1026
|
-
if (
|
|
1012
|
+
if (stagedSnapshot) {
|
|
1027
1013
|
const current = await readTaskRecord(ctx.cwd, taskId);
|
|
1028
1014
|
if (current.record?.intent_snapshot.revision === priorIntent.intent.revision) restoreStagedIntent();
|
|
1029
1015
|
}
|
|
@@ -1069,19 +1055,14 @@ export default function (
|
|
|
1069
1055
|
// Helpers (module scope; no workflow state)
|
|
1070
1056
|
// ---------------------------------------------------------------------------
|
|
1071
1057
|
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1058
|
+
// The derivation is shared with the Claude adapter so it cannot drift between
|
|
1059
|
+
// Hosts; this module re-exports it for its existing callers and tests.
|
|
1060
|
+
import {
|
|
1061
|
+
deriveAuthorizationOperation,
|
|
1062
|
+
type DerivedAuthorizationOperation,
|
|
1063
|
+
} from "../runtime/authorization_operation";
|
|
1075
1064
|
|
|
1076
|
-
|
|
1077
|
-
export function deriveAuthorizationOperation(input: {
|
|
1078
|
-
readiness: AssuranceAuthorizationReadiness;
|
|
1079
|
-
}): { operation: DerivedAuthorizationOperation } | { blocked: string } {
|
|
1080
|
-
if (input.readiness.state === "resolve_user_decision") return { operation: "resolve-user-decision" };
|
|
1081
|
-
if (input.readiness.state === "authorize_rework") return { operation: "authorize-rework" };
|
|
1082
|
-
if (input.readiness.blocked) return { blocked: input.readiness.blocked };
|
|
1083
|
-
return { blocked: "no unique host-derived authorization operation" };
|
|
1084
|
-
}
|
|
1065
|
+
export { deriveAuthorizationOperation, type DerivedAuthorizationOperation };
|
|
1085
1066
|
|
|
1086
1067
|
/**
|
|
1087
1068
|
* Ordinary Kernel operation mapping, exported so the conformance suite can
|
|
@@ -1142,7 +1123,7 @@ export async function recordCancelledUserDecision(
|
|
|
1142
1123
|
acceptance_id: null,
|
|
1143
1124
|
summary: `${operation} confirmation cancelled by literal user; snapshot ${snapshotDigestRef}`,
|
|
1144
1125
|
},
|
|
1145
|
-
actor_id:
|
|
1126
|
+
actor_id: LITERAL_USER_ACTOR_ID,
|
|
1146
1127
|
} as never,
|
|
1147
1128
|
prior_intent_token: (await readTaskIntent(ctx.cwd, taskId)).token,
|
|
1148
1129
|
diffProvider: (root: string, record: NonNullable<TaskRecordRead["record"]>) => diffSnapshotOf(root, record),
|
|
@@ -1178,9 +1159,9 @@ function diffSnapshotOf(root: string, record: NonNullable<TaskRecordRead["record
|
|
|
1178
1159
|
if (record.contract === "assurance_kernel/task_record/v4") {
|
|
1179
1160
|
if (!record.git_base_head)
|
|
1180
1161
|
throw new Error("TaskRecord v4 is missing git_base_head");
|
|
1181
|
-
return taskRevisionIdentity(root, record.intent_snapshot.scope_hint, record.git_base_head);
|
|
1162
|
+
return taskRevisionIdentity(root, record.intent_snapshot.scope_hint, record.git_base_head, record.task_id);
|
|
1182
1163
|
}
|
|
1183
|
-
return taskDiffIdentity(root, record.intent_snapshot.scope_hint);
|
|
1164
|
+
return taskDiffIdentity(root, record.intent_snapshot.scope_hint, record.task_id);
|
|
1184
1165
|
}
|
|
1185
1166
|
|
|
1186
1167
|
function diffHashOf(root: string, record: NonNullable<TaskRecordRead["record"]>): string {
|
|
@@ -1402,6 +1383,7 @@ async function applyAssuranceVerdict(
|
|
|
1402
1383
|
const capability = await mintCapability(registry, {
|
|
1403
1384
|
authority_kind: authorityKind,
|
|
1404
1385
|
task_id: snapshot.task_id,
|
|
1386
|
+
run_id: snapshot.run_id,
|
|
1405
1387
|
action_kind: "request_rework",
|
|
1406
1388
|
expected_record_hash: snapshot.record_revision,
|
|
1407
1389
|
intent_revision: snapshot.intent_revision,
|
|
@@ -1438,6 +1420,7 @@ async function applyAssuranceVerdict(
|
|
|
1438
1420
|
return;
|
|
1439
1421
|
}
|
|
1440
1422
|
const now = new Date().toISOString();
|
|
1423
|
+
const advisories = verdict.decision === "pass" ? reviewAdvisoryRecords(verdict) : [];
|
|
1441
1424
|
const approval = {
|
|
1442
1425
|
id: `approval-${snapshot.role}-${randomUUID().slice(0, 8)}`,
|
|
1443
1426
|
kind: snapshot.role === "qa" ? "qa" : "review",
|
|
@@ -1452,10 +1435,14 @@ async function applyAssuranceVerdict(
|
|
|
1452
1435
|
...(snapshot.role === "review" && snapshot.review_revision
|
|
1453
1436
|
? { review_revision: snapshot.review_revision }
|
|
1454
1437
|
: {}),
|
|
1438
|
+
...(snapshot.role === "review" && advisories.length > 0
|
|
1439
|
+
? { advisory_findings: advisories }
|
|
1440
|
+
: {}),
|
|
1455
1441
|
};
|
|
1456
1442
|
const capability = await mintCapability(registry, {
|
|
1457
1443
|
authority_kind: snapshot.role,
|
|
1458
1444
|
task_id: snapshot.task_id,
|
|
1445
|
+
run_id: snapshot.run_id,
|
|
1459
1446
|
action_kind: "record_approval",
|
|
1460
1447
|
expected_record_hash: snapshot.record_revision,
|
|
1461
1448
|
intent_revision: snapshot.intent_revision,
|
|
@@ -1481,7 +1468,6 @@ async function buildAssuranceSnapshot(
|
|
|
1481
1468
|
taskId: string,
|
|
1482
1469
|
role: AssuranceRole,
|
|
1483
1470
|
projection: AssuranceProjectionResult,
|
|
1484
|
-
runner: FrozenRunner,
|
|
1485
1471
|
): Promise<{
|
|
1486
1472
|
snapshot: SnapshotDescriptor;
|
|
1487
1473
|
descriptors: Map<string, VerificationDescriptor>;
|
|
@@ -1500,12 +1486,9 @@ async function buildAssuranceSnapshot(
|
|
|
1500
1486
|
const intent = record.record.intent_snapshot;
|
|
1501
1487
|
const acceptance = intent.acceptance;
|
|
1502
1488
|
const descriptors = new Map<string, VerificationDescriptor>();
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
// a free-form or version-mismatched string is ineligible.
|
|
1506
|
-
for (const item of acceptance) {
|
|
1489
|
+
// Review resumes from settled outcomes without resolving verification tools.
|
|
1490
|
+
for (const item of role === "qa" ? acceptance : []) {
|
|
1507
1491
|
const descriptor = parseVerificationDescriptor(item.verification);
|
|
1508
|
-
assertRunnerCompatible(descriptor, runner);
|
|
1509
1492
|
descriptors.set(item.id, descriptor);
|
|
1510
1493
|
}
|
|
1511
1494
|
// `git_base_head` is optional on the read shape because v3 records carry
|
|
@@ -1561,6 +1544,7 @@ async function buildAssuranceSnapshot(
|
|
|
1561
1544
|
root,
|
|
1562
1545
|
task_id: taskId,
|
|
1563
1546
|
role,
|
|
1547
|
+
run_id: projection.projection.run_id,
|
|
1564
1548
|
record_revision: projection.projection.record_revision,
|
|
1565
1549
|
workspace_revision: projection.projection.workspace_revision,
|
|
1566
1550
|
intent_revision: projection.projection.intent_revision,
|
|
@@ -1602,12 +1586,6 @@ function qaOutcomes(
|
|
|
1602
1586
|
);
|
|
1603
1587
|
}
|
|
1604
1588
|
|
|
1605
|
-
let frozenRunnerValue: FrozenRunner | undefined;
|
|
1606
|
-
async function frozenRunner(): Promise<FrozenRunner> {
|
|
1607
|
-
frozenRunnerValue ??= resolveBunRunner();
|
|
1608
|
-
return frozenRunnerValue;
|
|
1609
|
-
}
|
|
1610
|
-
|
|
1611
1589
|
// The invocation registry is shared with the progression module's
|
|
1612
1590
|
// module-scoped registry (see the top-level import above); commit/cancel
|
|
1613
1591
|
// semantics are identical to the previous extension implementation.
|
|
@@ -1617,6 +1595,7 @@ async function mintCapability(
|
|
|
1617
1595
|
input: {
|
|
1618
1596
|
authority_kind: "review" | "qa" | "user";
|
|
1619
1597
|
task_id: string;
|
|
1598
|
+
run_id: string | null;
|
|
1620
1599
|
action_kind: string;
|
|
1621
1600
|
expected_record_hash: string;
|
|
1622
1601
|
intent_revision: number;
|
|
@@ -1654,6 +1633,7 @@ async function mintCapability(
|
|
|
1654
1633
|
const binding: CapabilityBindingV2 = {
|
|
1655
1634
|
authority_kind: input.authority_kind,
|
|
1656
1635
|
task_id: input.task_id,
|
|
1636
|
+
...(input.run_id ? { run_id: input.run_id } : {}),
|
|
1657
1637
|
action_digest: digest,
|
|
1658
1638
|
expected_record_hash: input.expected_record_hash,
|
|
1659
1639
|
intent_revision: input.intent_revision,
|
|
@@ -1698,13 +1678,8 @@ async function executeOrdinaryOperation(
|
|
|
1698
1678
|
// A content-changing revision writes the sidecar before the kernel's drift
|
|
1699
1679
|
// check runs; an unstaged write is itself scoped drift and deadlocks the
|
|
1700
1680
|
// revision. Mirror the breaking-revision path: stage the written sidecar
|
|
1701
|
-
// (worktree == index) and restore the exact prior
|
|
1702
|
-
const
|
|
1703
|
-
? execFileSync("git", ["ls-files", "--stage", "-z", "--", priorIntent.intent_ref.path], {
|
|
1704
|
-
cwd: ctx.cwd,
|
|
1705
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1706
|
-
})
|
|
1707
|
-
: null;
|
|
1681
|
+
// (worktree == index) and restore the exact prior state on failure.
|
|
1682
|
+
const priorStaged = priorBytes !== null ? captureStagedIntent(ctx.cwd, priorIntent.intent_ref.path) : null;
|
|
1708
1683
|
try {
|
|
1709
1684
|
if (priorBytes) {
|
|
1710
1685
|
writeFileSync(sidecar, `${JSON.stringify(operation.next_intent, null, 2)}\n`);
|
|
@@ -1730,19 +1705,8 @@ async function executeOrdinaryOperation(
|
|
|
1730
1705
|
} catch (error) {
|
|
1731
1706
|
if (priorBytes) {
|
|
1732
1707
|
const current = await readTaskRecord(ctx.cwd, input.taskId);
|
|
1733
|
-
if (current.record?.intent_snapshot.revision === priorIntent.intent.revision) {
|
|
1734
|
-
|
|
1735
|
-
execFileSync("git", ["update-index", "--force-remove", "--", priorIntent.intent_ref.path], {
|
|
1736
|
-
cwd: ctx.cwd,
|
|
1737
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1738
|
-
});
|
|
1739
|
-
if (priorIndexState && priorIndexState.length > 0) {
|
|
1740
|
-
execFileSync("git", ["update-index", "-z", "--index-info"], {
|
|
1741
|
-
cwd: ctx.cwd,
|
|
1742
|
-
input: priorIndexState,
|
|
1743
|
-
stdio: ["pipe", "ignore", "pipe"],
|
|
1744
|
-
});
|
|
1745
|
-
}
|
|
1708
|
+
if (current.record?.intent_snapshot.revision === priorIntent.intent.revision && priorStaged) {
|
|
1709
|
+
restoreStagedIntentShared(ctx.cwd, priorStaged);
|
|
1746
1710
|
}
|
|
1747
1711
|
}
|
|
1748
1712
|
throw error;
|
|
@@ -1774,24 +1738,15 @@ async function enrichAssuranceResult(
|
|
|
1774
1738
|
: projection.projection;
|
|
1775
1739
|
let tracker: Awaited<ReturnType<typeof markGithubTaskTerminal>> | undefined;
|
|
1776
1740
|
await reconcileRefsQuietly(ctx.cwd);
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
tracker = {
|
|
1787
|
-
contract: "immune_brain/github_issue_tracker_result/v1",
|
|
1788
|
-
operation: "mark-terminal",
|
|
1789
|
-
status: "retryable_failure",
|
|
1790
|
-
association_found: false,
|
|
1791
|
-
message: "tracker observation failed after authoritative settlement",
|
|
1792
|
-
};
|
|
1793
|
-
}
|
|
1794
|
-
}
|
|
1741
|
+
// One shared projection: the Claude Host runs the same step, so the opted-in
|
|
1742
|
+
// terminal tracker outcome does not depend on which Host settled the task.
|
|
1743
|
+
tracker = await projectTerminalTrackerState({
|
|
1744
|
+
root: ctx.cwd,
|
|
1745
|
+
task_id: taskId,
|
|
1746
|
+
projection,
|
|
1747
|
+
tombstone: await readTaskTombstone(ctx.cwd, taskId),
|
|
1748
|
+
markTerminal: (root, input) => markGithubTaskTerminal(root, input),
|
|
1749
|
+
});
|
|
1795
1750
|
return {
|
|
1796
1751
|
...result,
|
|
1797
1752
|
task_state: taskState,
|