elanous 0.2.27 → 0.2.28
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/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/{1454-a9c338f5bb556f42.js → 1454-29d1d773d5996a49.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{599-a914cc0468f56143.js → 599-56f2c0a5cc4a7295.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-5ab1e47978577c5e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-07110da72c45e68f.js → page-24453f6a302816b4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/{page-d2d76d4a7eae91bd.js → page-6c131d30bb1cf871.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-fa46d4588e8442c2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-e5a9302c105725a3.js → page-dc0772b11861af51.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-66ad46ed0279feb9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-5159dc8752c6cd29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-c5a2478861c08b87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-f722a9dec084e08b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/{page-6a16a48d1aa6e91f.js → page-256ffd365d32e100.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-e0de78ae7b99f01c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-cfbdc44b708d9801.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-39f82acf619cca23.js → page-4c741eb760f2f77c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-7c2c8f81b185f9c0.js → page-e9ba98f5f1440a21.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/{page-ae7aff07e32676bb.js → page-dafc901bb1b72b5f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-482c60c1bc540d0a.js → page-2f6f303fa114ec24.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{layout-7cb996c1aa08b6f5.js → layout-1634081c5272006c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/{page-623e08fe4477d606.js → page-ced0bcfc6542b3fc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/{page-db460cd8aecb8490.js → page-bb1f05cd51c38944.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/{page-ff27902d0b11ef4f.js → page-646b50f63b6fe732.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-db069b48a59c8c00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-1917ea87ea71ab6b.js → page-f736324afa2117c1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-25a302273b054fd7.js → page-6f131b51193a36f9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-670510eda8b9443f.js → not-found-435f5e2faf02bc01.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-497e0eedf20e7890.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-a8d28fdf995e16ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-4bb7556e2e97a750.js → page-8966452228753c5e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fd5d18656cb432b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-5663779cb1a37609.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/{page-4eb96fc7241c2456.js → page-66a67eb19ebd976b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-3b492aa78cf3c4a6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-0d64ff212dc55d03.js → page-c71267e7681da982.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-7f096248993fc2d4.js → page-a21d1e1797172692.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-e47fc72049149a38.js → page-cb249e81ed339318.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-f7d808e31e005895.js → page-781cfc5114e097da.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-5c2b24b5496dad73.js → page-478157214db6a023.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-76cdb531668b5191.js → page-86141f12cf090b7f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-7256c73bb1c36751.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-5c738ca7adef3de5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-0138c8091ec17224.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-cc2f24f16d85e735.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/{page-dab163b338e3b966.js → page-979d318a534e92da.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-182d394f7e6e096b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/{page-0260ffc7eda50db1.js → page-21050928a697ed7a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-44b9d807005a9030.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-99c4e6c7345a9156.js → page-601fb77340e63c9a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-75d3fb5d47af0edf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-11bf9e5725a72c22.js +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +13 -13
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +13 -13
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +13 -13
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +13 -13
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +13 -13
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +13 -13
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +13 -13
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +13 -13
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +13 -13
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +13 -13
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +13 -13
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +13 -13
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +13 -13
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +13 -13
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +13 -13
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +13 -13
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +13 -13
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +13 -13
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +13 -13
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +13 -13
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +13 -13
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +13 -13
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +13 -13
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +13 -13
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +13 -13
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +13 -13
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +13 -13
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +13 -13
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +14 -14
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +13 -13
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +13 -13
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +13 -13
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +13 -13
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +13 -13
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +13 -13
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +13 -13
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +13 -13
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +13 -13
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +13 -13
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +13 -13
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +13 -13
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +13 -13
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +13 -13
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +13 -13
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +13 -13
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +13 -13
- package/graphs/implement-loop.yaml +38 -1
- package/graphs/steward/recipes.yaml +6 -0
- package/graphs/steward/release-day.yaml +23 -0
- package/graphs/steward/steward.yaml +9 -1
- package/graphs/work/code-pr.yaml +23 -0
- package/package.json +1 -1
- package/scripts/browser-observe-cycle.ts +76 -2
- package/scripts/ci-typecheck-changed.ts +50 -17
- package/scripts/detect-test-interference.ts +31 -1
- package/scripts/release-loop/docs-node.ts +51 -22
- package/scripts/release-loop/gate-node.ts +37 -4
- package/scripts/release-loop/node-verdict.ts +3 -1
- package/scripts/release-loop/publish-node.ts +3 -0
- package/scripts/release-loop/tui-node-checks.ts +5 -2
- package/scripts/skills/public-check.ts +5 -2
- package/scripts/test-deterministic.ts +520 -37
- package/scripts/ux-sim.ts +7 -1
- package/scripts/verify-kpack6-live.ts +230 -0
- package/src/acp/codex-plugins.ts +17 -0
- package/src/agent/task-notification.ts +2 -1
- package/src/agent-mission/in-agent-plugin.ts +8 -3
- package/src/agent-substrate/self-review-cli.ts +34 -10
- package/src/briefing/brief-items.ts +151 -35
- package/src/budget/llm-cost.ts +25 -2
- package/src/budget/usage-outcomes.ts +32 -0
- package/src/claims/claims-ledger.ts +23 -0
- package/src/claims/claims-render.ts +22 -2
- package/src/cli/brief-cli.ts +131 -10
- package/src/cli/claims-cli.ts +24 -4
- package/src/cli/msg-cli.ts +165 -9
- package/src/cli/plugin-cli.ts +11 -3
- package/src/cli/pr-cli.ts +80 -18
- package/src/cli/pr-land-refusal.ts +20 -0
- package/src/cli/release-cli.ts +2 -0
- package/src/cli/rfc-cli.ts +24 -0
- package/src/cli/tailscale-serve.ts +5 -8
- package/src/cli/tasks-cli.ts +393 -26
- package/src/cli/usage-cli.ts +9 -4
- package/src/dashboard/first-screen-band.ts +63 -13
- package/src/dashboard/index.ts +8 -3
- package/src/dashboard/skill-detection-runtime.ts +24 -4
- package/src/domains/morning-report.ts +3 -1
- package/src/github/pr-labels.ts +10 -4
- package/src/graph-runner/runner.ts +101 -14
- package/src/harness/harness-clean.ts +21 -3
- package/src/harness/harness-cli-command.ts +22 -6
- package/src/harness/harness-pod-dispatch.ts +11 -4
- package/src/hq/seats.ts +17 -3
- package/src/index.ts +34 -5
- package/src/intelligence-map/model-bench-scores.ts +102 -0
- package/src/loops/orchestrator/inbox.ts +73 -0
- package/src/loops/orchestrator/resync-shadow.ts +100 -12
- package/src/loops/orchestrator/tick.ts +73 -2
- package/src/maturity/feature-maturity.ts +1 -1
- package/src/msg/msg-sse-server.ts +141 -0
- package/src/msg/msg-store.ts +84 -14
- package/src/patents/ledger.ts +128 -0
- package/src/plugins/install/market-fetch.ts +32 -1
- package/src/pod-lease/admission.ts +116 -41
- package/src/release-loop/checklist.ts +11 -0
- package/src/release-loop/merged-pr-checklist.ts +24 -5
- package/src/release-loop/rubric.ts +70 -0
- package/src/rfc-registry/registry.ts +88 -0
- package/src/seat-dispatch/seat-request-ledger.ts +4 -0
- package/src/seat-loop/knowhow-lint.ts +233 -0
- package/src/seat-loop/seat-pack.ts +16 -4
- package/src/seat-loop/traffic-signal.ts +7 -0
- package/src/self-dev/ask-launch-flow.ts +48 -8
- package/src/self-dev/ask-path-candidates.ts +69 -14
- package/src/self-dev/decompose-proposal.ts +43 -1
- package/src/self-dev/graph-journey-nodes.ts +2 -0
- package/src/self-dev/launch-preflight-text.ts +5 -1
- package/src/self-dev/launch-preflight.ts +143 -10
- package/src/self-implement/gate-cli.ts +6 -2
- package/src/self-implement/goal-author.ts +2 -1
- package/src/self-implement/goal-new-paths.ts +4 -5
- package/src/self-implement/graph-authority.ts +17 -3
- package/src/self-implement/graph-yaml.ts +15 -7
- package/src/self-implement/headless-elanous-driver.ts +16 -1
- package/src/self-implement/host-regate.ts +44 -9
- package/src/self-implement/orchestrator.ts +3 -10
- package/src/self-implement/rework-policy.ts +80 -2
- package/src/self-implement/speed-claims.ts +145 -6
- package/src/skills/tools/persistent-grounding.ts +59 -4
- package/src/skills/tools/shell.ts +79 -1
- package/src/steward/rejudge-tick.ts +12 -1
- package/src/steward/release-day.ts +158 -0
- package/src/steward/resync-tick.ts +123 -0
- package/src/task-agent/actions.ts +1 -0
- package/src/task-agent/adopt-pr.ts +140 -0
- package/src/task-agent/board.ts +11 -1
- package/src/task-agent/card-evidence.ts +37 -5
- package/src/task-agent/cover.ts +84 -17
- package/src/task-agent/failure-notify.ts +61 -0
- package/src/task-agent/green-sweep.ts +170 -0
- package/src/task-agent/land-worktree.ts +52 -5
- package/src/task-agent/live-move-history.ts +1 -0
- package/src/task-agent/live-moves.ts +43 -12
- package/src/task-agent/parent-host.ts +1 -1
- package/src/task-agent/postland-card.ts +139 -0
- package/src/task-agent/rejudge-on-head.ts +106 -7
- package/src/task-agent/remote-rejudge-bind.ts +62 -0
- package/src/task-agent/repair-branch-cas.ts +182 -0
- package/src/task-agent/repair-cycle.ts +275 -0
- package/src/task-agent/repair-next-move.ts +31 -0
- package/src/task-agent/repair-review.ts +24 -0
- package/src/task-agent/repair-worker.ts +87 -0
- package/src/task-agent/review-result-capture.ts +2 -1
- package/src/task-agent/shadow.ts +13 -7
- package/src/task-agent/task-hand.ts +115 -6
- package/src/task-orchestrator/surfaces/pod-repair-commit.ts +96 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +70 -12
- package/src/telegram.ts +30 -1
- package/src/user-config.ts +8 -1
- package/src/ux-sim/fault-sqlite-busy.ts +50 -0
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-a1ce306364f08da9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-30007380f2e2a3c1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-b93c04b5311c97ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-718a49e86bf25f9c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-20662900d6b4b455.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-f707a509515ac50c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-fcb06e305f515527.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-6acf5ec4e6c6d1af.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-8397a5c6ca569bf4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-d73c3f1e00d93986.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-65caacf54389081e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-77e04b4f8762c666.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-15c0a4095baddfba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-63e272a4cd3b6634.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-e2b63345bba1a11f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-d376e0c83edc041a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-d6db55fe2c04d128.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-87b88f551f421d8b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-08019efef111ed2e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-980ce6fe36ad95eb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-0ab42e0f05823013.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ad9220422d20ba33.js +0 -1
- /package/apps/pwa/out/_next/static/{Ev2jivDMTSoEc-5rIXdcy → y1ltt7oCZxVpC6zVPhZ2O}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{Ev2jivDMTSoEc-5rIXdcy → y1ltt7oCZxVpC6zVPhZ2O}/_ssgManifest.js +0 -0
package/src/task-agent/shadow.ts
CHANGED
|
@@ -15,8 +15,8 @@ import { extname, join, relative, resolve } from 'node:path';
|
|
|
15
15
|
import { debug } from '../debug/log.js';
|
|
16
16
|
import { readTaskAgentState, readTaskCard, taskAgentStatePath, TASK_CARD_PREFIX, type CompletionKind, type TaskCard } from './task-hand.js';
|
|
17
17
|
import type { SupervisorJobResult, SupervisorStopReason } from '../self-dev/run-supervisor.js';
|
|
18
|
-
import { executeNextAction, type DeliveryKind, type NextAction } from './actions.js';
|
|
19
|
-
import { judgeNextMove, type TaskJudgeInput, type TaskJudgement } from './judge.js';
|
|
18
|
+
import { executeNextAction, type ActionDeps, type DeliveryKind, type NextAction } from './actions.js';
|
|
19
|
+
import { judgeNextMove, LIVE_RETRY_MOVES, type TaskJudgeInput, type TaskJudgement } from './judge.js';
|
|
20
20
|
import { HARNESS_RUN_ID_ENV } from '../harness/harness-space.js';
|
|
21
21
|
import { appendRunLedgerEntry, loadRunLedger } from '../self-implement/run-ledger.js';
|
|
22
22
|
import { collectWiringEvidence, readCardRunSnapshot, type WiringEvidenceDeps, type CardRunEvidenceDeps } from './card-evidence.js';
|
|
@@ -57,8 +57,10 @@ export interface TaskAgentShadowMove {
|
|
|
57
57
|
|
|
58
58
|
export interface TaskAgentShadowDeps {
|
|
59
59
|
log?: (category: string, event: string, data: Record<string, unknown>) => void;
|
|
60
|
-
/**
|
|
61
|
-
command?:
|
|
60
|
+
/** 재발사 live 실행부 명령(주입되지 않으면 shadow). */
|
|
61
|
+
command?: ActionDeps['command'];
|
|
62
|
+
/** 재발사 live 부하 관문(주입되지 않으면 shadow). */
|
|
63
|
+
loadGate?: ActionDeps['loadGate'];
|
|
62
64
|
readCard?: (taskId: string) => TaskCard | undefined;
|
|
63
65
|
/** Explicit live invocation only; supervisor's default hook always uses shadow. */
|
|
64
66
|
mode?: 'shadow' | 'live';
|
|
@@ -265,6 +267,9 @@ export async function recordTaskAgentShadowMove(input: TaskAgentShadowInput, dep
|
|
|
265
267
|
: { move: 'wait', stage: 'waiting', reason: '전달 후보 없음' }
|
|
266
268
|
: judgeNextMove(judgeInput);
|
|
267
269
|
if (cardLookupFailed) judgement.reason = `${judgement.reason} · 종결 종류 미확인(카드 조회 실패)`;
|
|
270
|
+
// 판단마다 해석한다 — 허용되지 않은 수를 판단할 때도 모르는 항목 경고가 남게.
|
|
271
|
+
const liveMoves = deps.liveMoves ?? await configuredTaskAgentLiveMoves();
|
|
272
|
+
const retryLaunches = { count: 0 };
|
|
268
273
|
let executorResult: string | null = null;
|
|
269
274
|
let wouldDo: string | null = null;
|
|
270
275
|
if (judgement.executorKind) {
|
|
@@ -279,10 +284,13 @@ export async function recordTaskAgentShadowMove(input: TaskAgentShadowInput, dep
|
|
|
279
284
|
checklistId: delivery ? card?.checklistId ?? '' : '',
|
|
280
285
|
...(delivery ? { delivery } : {}),
|
|
281
286
|
};
|
|
287
|
+
const liveRetry = !delivery && deps.command !== undefined && deps.loadGate !== undefined
|
|
288
|
+
&& (LIVE_RETRY_MOVES as readonly string[]).includes(judgement.move) && liveMoves.has(judgement.move as TaskAgentLiveMove);
|
|
282
289
|
executorResult = await executeNextAction(action, {
|
|
283
|
-
mode: delivery ? (deps.mode ?? 'shadow') : 'shadow',
|
|
290
|
+
mode: delivery ? (deps.mode ?? 'shadow') : liveRetry ? 'live' : 'shadow',
|
|
284
291
|
command: deps.command ?? (async () => { throw new Error('task-agent shadow path must not run commands'); }),
|
|
285
292
|
observe: (_event, data) => { wouldDo = data.reason; },
|
|
293
|
+
...(liveRetry ? { liveMoves, retryLaunches, loadGate: deps.loadGate } : {}),
|
|
286
294
|
});
|
|
287
295
|
}
|
|
288
296
|
const move: TaskAgentShadowMove = {
|
|
@@ -305,8 +313,6 @@ export async function recordTaskAgentShadowMove(input: TaskAgentShadowInput, dep
|
|
|
305
313
|
move.wouldDo = null;
|
|
306
314
|
}
|
|
307
315
|
const liveKind: TaskAgentLiveMove | null = move.move === 'review' || move.move === 'propose-green' || move.move === 'propose-land' || move.move === 'decision-card' ? move.move : null;
|
|
308
|
-
// 판단마다 해석한다 — 허용되지 않은 수를 판단할 때도 모르는 항목 경고가 남게.
|
|
309
|
-
const liveMoves = deps.liveMoves ?? await configuredTaskAgentLiveMoves();
|
|
310
316
|
// 카드 id 는 모든 판단 관측에 싣는다(그림자 일치율을 카드로 잇는다) — 읽기 전용 조회.
|
|
311
317
|
// live 수는 «근거 결과»에 묶는다 — PR 수는 그 PR 을 낸 결과의 카드·작업 트리, 전달은 전달한 카드(다중 결과에서 남의 카드에 쓰지 않게).
|
|
312
318
|
const prResult = typeof judgeInput.pr === 'number' ? input.results.find((result) => result.prNumber === judgeInput.pr) : undefined;
|
|
@@ -11,6 +11,10 @@ import { execFileSync } from 'node:child_process';
|
|
|
11
11
|
import { createHash, randomBytes } from 'node:crypto';
|
|
12
12
|
import { mkdirSync, readFileSync, statSync, writeFileSync, type Stats } from 'node:fs';
|
|
13
13
|
import { dirname, join, resolve } from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { parse as parseYaml } from 'yaml';
|
|
16
|
+
import { validateUnitCalls } from '../graph-runner/runner.js';
|
|
17
|
+
import { parseGraphTemplateYaml } from '../self-implement/graph-yaml.js';
|
|
14
18
|
import { resolveDaemonHarnessTarget } from '../intake-plane/harness-target.js';
|
|
15
19
|
import { HARNESS_RUN_ID_ENV, mintRunId, normalizeRunId } from '../harness/harness-space.js';
|
|
16
20
|
import { debug } from '../debug/log.js';
|
|
@@ -89,6 +93,66 @@ export function taskCardLabelContext(card: Pick<TaskCard, 'id'>): TaskCardLabelC
|
|
|
89
93
|
export const COMPLETION_KINDS = ['code-pr', 'artifact', 'research-report', 'ops-action', 'content', 'watch-brief', 'decision-support', 'deliverable'] as const;
|
|
90
94
|
export type CompletionKind = typeof COMPLETION_KINDS[number];
|
|
91
95
|
|
|
96
|
+
export interface TaskComposition {
|
|
97
|
+
file: string;
|
|
98
|
+
/** null = 파일은 있으나 파싱 실패로 판을 확인하지 못했다. */
|
|
99
|
+
version: number | null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface CompositionSelection {
|
|
103
|
+
composition?: TaskComposition;
|
|
104
|
+
unitsResolved: boolean;
|
|
105
|
+
missingUnits: string[];
|
|
106
|
+
launcher?: 'harness-say' | 'graph-run';
|
|
107
|
+
error?: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** 1판은 조합을 고르고 단위 해석 결과를 기록한다. 단위 실행은 2판의 몫이다. */
|
|
111
|
+
export function selectTaskComposition(completion: CompletionKind, root = join(dirname(fileURLToPath(import.meta.url)), '..', '..')): CompositionSelection {
|
|
112
|
+
const file = `graphs/work/${completion}.yaml`;
|
|
113
|
+
const path = join(root, file);
|
|
114
|
+
let source: string;
|
|
115
|
+
try { source = readFileSync(path, 'utf8'); } catch (error) {
|
|
116
|
+
if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') return { unitsResolved: false, missingUnits: [] };
|
|
117
|
+
return { composition: { file, version: null }, unitsResolved: false, missingUnits: [],
|
|
118
|
+
error: `조합 읽기 오류: ${file}: ${error instanceof Error ? error.message : String(error)}` };
|
|
119
|
+
}
|
|
120
|
+
const parsed = parseGraphTemplateYaml(source, path);
|
|
121
|
+
if (!parsed.template || parsed.errors.length) {
|
|
122
|
+
let version: number | null = null;
|
|
123
|
+
try {
|
|
124
|
+
const raw: unknown = parseYaml(source);
|
|
125
|
+
if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
|
|
126
|
+
const candidate = (raw as Record<string, unknown>).version;
|
|
127
|
+
if (typeof candidate === 'number' && Number.isInteger(candidate)) version = candidate;
|
|
128
|
+
}
|
|
129
|
+
} catch { /* 파싱 실패 자체를 아래 원장에 적는다. */ }
|
|
130
|
+
return { composition: { file, version }, unitsResolved: false, missingUnits: [],
|
|
131
|
+
error: `조합 오류: ${file}: ${parsed.errors.map(e => e.message).join('; ')}` };
|
|
132
|
+
}
|
|
133
|
+
const raw: unknown = parseYaml(source);
|
|
134
|
+
const launcher = raw && typeof raw === 'object' && !Array.isArray(raw) ? (raw as Record<string, unknown>).launcher : undefined;
|
|
135
|
+
if (launcher !== undefined && launcher !== 'harness-say' && launcher !== 'graph-run') {
|
|
136
|
+
return { composition: { file, version: parsed.template.version }, unitsResolved: false, missingUnits: [],
|
|
137
|
+
error: `조합 오류: ${file}: 모르는 launcher` };
|
|
138
|
+
}
|
|
139
|
+
if (completion === 'code-pr' && launcher === undefined) {
|
|
140
|
+
return { composition: { file, version: parsed.template.version }, unitsResolved: false, missingUnits: [],
|
|
141
|
+
error: `조합 오류: ${file}: launcher 없음` };
|
|
142
|
+
}
|
|
143
|
+
const missingUnits: string[] = [];
|
|
144
|
+
let unitError: string | undefined;
|
|
145
|
+
try { validateUnitCalls(parsed.template, path, [], call => missingUnits.push(call)); }
|
|
146
|
+
catch (error) { unitError = error instanceof Error ? error.message : String(error); }
|
|
147
|
+
return {
|
|
148
|
+
composition: { file, version: parsed.template.version },
|
|
149
|
+
unitsResolved: missingUnits.length === 0 && !unitError,
|
|
150
|
+
missingUnits,
|
|
151
|
+
...(launcher === 'harness-say' || launcher === 'graph-run' ? { launcher } : {}),
|
|
152
|
+
...(unitError ? { error: `조합 단위 해석 오류: ${file}: ${unitError}` } : {}),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
92
156
|
/**
|
|
93
157
|
* `run-bound`·`goal-bound`·`pr-bound` 은 runId(⊕ pr)를 싣는다 — 옛 카드의 칸은 at·event·detail 뿐이다.
|
|
94
158
|
* TA-LIVE-MOVE-CARD-HISTORY — `live-move` 줄만 kind·executed·ok·executorResult·head·effect 를 더 싣는다(`live-move-history.ts`).
|
|
@@ -104,6 +168,8 @@ export interface TaskCardEvent {
|
|
|
104
168
|
resultPath?: string;
|
|
105
169
|
/** TA-REVIEW-RESULT-CAPTURE — `live-move-result` 줄만: 회수한 `self review --json` 의 칸(못 읽은 칸은 null). */
|
|
106
170
|
verdict?: string | null; reviewed?: boolean | null; mustFix?: number | null; reviewRoute?: string | null;
|
|
171
|
+
/** Exact bytes of the captured review stdout; repair must read the same resultPath and digest. */
|
|
172
|
+
reviewSha256?: string;
|
|
107
173
|
}
|
|
108
174
|
/** 카드 history 의 live-move 줄 이벤트 이름 — 상태 사유(«마지막 칸») 판정에서 뺀다. */
|
|
109
175
|
export const LIVE_MOVE_HISTORY_EVENT = 'live-move';
|
|
@@ -117,8 +183,15 @@ export interface TaskCard {
|
|
|
117
183
|
text: string;
|
|
118
184
|
seat?: TaskSeat;
|
|
119
185
|
checklistId?: string;
|
|
186
|
+
/** Explicit release version for an adopted PR; post-land still confirms the cell exists there. */
|
|
187
|
+
checklistVersion?: string;
|
|
120
188
|
/** 종결 종류 — 비면 code-pr. */
|
|
121
189
|
completion?: CompletionKind;
|
|
190
|
+
/** 선택된 일 조합 파일과 판(1판: 실행은 launcher 에 위임). */
|
|
191
|
+
composition?: TaskComposition;
|
|
192
|
+
graphSelectedBy?: 'completion';
|
|
193
|
+
unitsResolved?: boolean;
|
|
194
|
+
missingUnits?: string[];
|
|
122
195
|
/** 과제 대상 프로젝트(§A4b②) — target 은 절대 경로(git 이 아닐 수 있다). id 는 `task hand --project` 가 싣는다(옛 카드엔 없을 수 있다). */
|
|
123
196
|
project?: { id?: string; target: string };
|
|
124
197
|
/** 보드(§A4b① `board.ts`) 소속 — 목표 id · 이정표 id(`task hand --goal/--milestone`). 조각 카드의 `goalId`(골 sha 16자)와 다른 칸이다. */
|
|
@@ -130,7 +203,7 @@ export interface TaskCard {
|
|
|
130
203
|
* failed = 발사된 런이 PR·수확 가지 없이 죽었거나 끝내 안 떴다(TA-JUDGE-DEAD-RUN · `card-evidence.ts`) — 사유
|
|
131
204
|
* (`failed/needs-owner|needs-relaunch — …`)는 history 마지막 칸 · 자동 재발사하지 않는다(사람/판단부 몫).
|
|
132
205
|
*/
|
|
133
|
-
status: 'handed' | 'launched' | 'launch-failed' | 'failed';
|
|
206
|
+
status: 'handed' | 'launched' | 'launch-failed' | 'failed' | 'done';
|
|
134
207
|
history: TaskCardEvent[];
|
|
135
208
|
/** 미션 카드만 — 조각 카드 id(순서대로) · 쪼갠 출처(`mission.ts` splitMission). */
|
|
136
209
|
pieces?: string[];
|
|
@@ -145,6 +218,12 @@ export interface TaskCard {
|
|
|
145
218
|
* 번호는 `cardPrNumber` 로 읽는다(옛 카드의 number 도 그대로 읽힌다).
|
|
146
219
|
*/
|
|
147
220
|
pr?: number | TaskCardPr;
|
|
221
|
+
/** Host-verified existing OPEN PR adopted without launching a new implementation run. */
|
|
222
|
+
adoptedPr?: { pr: number; url: string; head: string; at: string };
|
|
223
|
+
/** Bound PR observed in a terminal GitHub state. Only applies while the card still binds the same PR and URL. */
|
|
224
|
+
prTerminal?: { pr: number; url: string; head: string; state: 'MERGED' | 'CLOSED'; at: string };
|
|
225
|
+
/** Post-land checklist judgement. A partial slice closes this card while leaving the checklist cell yellow. */
|
|
226
|
+
postLand?: { pr: number; head: string; at: string; outcome: 'complete' | 'partial'; evidence: string[]; remaining: string[] };
|
|
148
227
|
goalId?: string;
|
|
149
228
|
/** `manual` = pr/goalId 를 사람이 적었다(`recordPieceRef`) — 런 묶기가 건드리지 않는다. */
|
|
150
229
|
refSource?: 'manual';
|
|
@@ -185,6 +264,10 @@ export interface TaskCard {
|
|
|
185
264
|
taLand?: true;
|
|
186
265
|
/** TASKS-HAND-CHILD-LLM — `tasks hand --child-llm <provider>/<model> [--child-llm-effort <level>]` → 발사 인자 `--child-llm-provider/-model/-effort`. */
|
|
187
266
|
childLlm?: TaskChildLlm;
|
|
267
|
+
/** Same-PR repair launches; exact source head and worker result are retained for CAS and retry limits. */
|
|
268
|
+
repairs?: Array<{ pr: number; head: string; branch: string; at: string; move: string; runId: string;
|
|
269
|
+
resultPath: string; fromModel: string; toModel: string; status: 'requested' | 'unconfirmed' | 'failed' | 'applied';
|
|
270
|
+
detail?: string; commit?: string; bundleSha256?: string; bundlePath?: string }>;
|
|
188
271
|
}
|
|
189
272
|
|
|
190
273
|
/** 구현 자식 LLM 선택 — 하니스 `--child-llm-provider/--child-llm-model/--child-llm-effort` 로 그대로 넘긴다(검증 권위는 하니스). */
|
|
@@ -215,6 +298,12 @@ export function cardPrNumber(card: Pick<TaskCard, 'pr'>): number | undefined {
|
|
|
215
298
|
return typeof card.pr === 'number' ? card.pr : card.pr?.number;
|
|
216
299
|
}
|
|
217
300
|
|
|
301
|
+
/** A stale terminal marker must never close a card after its PR binding changes. */
|
|
302
|
+
export function boundPrTerminal(card: Pick<TaskCard, 'pr' | 'prTerminal'>): TaskCard['prTerminal'] | undefined {
|
|
303
|
+
const marker = card.prTerminal;
|
|
304
|
+
return marker && typeof card.pr === 'object' && card.pr.number === marker.pr && card.pr.url === marker.url ? marker : undefined;
|
|
305
|
+
}
|
|
306
|
+
|
|
218
307
|
/** A uniquely launched code-PR card may own a draft through its PR or its opening run. */
|
|
219
308
|
export function draftTaskOwner(cards: readonly TaskCard[], draft: { number: number; runId?: string | null }): TaskCard | undefined {
|
|
220
309
|
const owners = cards.filter((card) => card.status === 'launched' && (card.completion ?? 'code-pr') === 'code-pr'
|
|
@@ -374,6 +463,8 @@ export interface HandTaskOptions {
|
|
|
374
463
|
childLlm?: string;
|
|
375
464
|
childLlmEffort?: string;
|
|
376
465
|
statePath?: string;
|
|
466
|
+
/** 시험에서만 조합 저장소를 바꿔 해석·거부를 검증한다. */
|
|
467
|
+
compositionRoot?: string;
|
|
377
468
|
/** `--live` 일 때만 불린다. */
|
|
378
469
|
launcher?: TaskLauncher;
|
|
379
470
|
now?: () => Date;
|
|
@@ -443,6 +534,11 @@ export async function handTask(opts: HandTaskOptions): Promise<HandTaskResult> {
|
|
|
443
534
|
}
|
|
444
535
|
if (opts.childLlmEffort !== undefined && opts.childLlm === undefined) throw new Error('--child-llm-effort 는 --child-llm <provider>/<model> 과 함께 준다');
|
|
445
536
|
const childLlm = opts.childLlm !== undefined ? parseChildLlm(opts.childLlm, opts.childLlmEffort) : undefined;
|
|
537
|
+
const completion = opts.completion ?? 'code-pr';
|
|
538
|
+
const selected = selectTaskComposition(completion, opts.compositionRoot);
|
|
539
|
+
const selectionFailure = !selected.composition ? `조합 없음: graphs/work/${completion}.yaml`
|
|
540
|
+
: selected.error ?? (completion !== 'code-pr' || selected.launcher !== 'harness-say'
|
|
541
|
+
? `조합 실행기 없음: ${selected.composition.file} (1판은 code-pr harness-say만 발사)` : undefined);
|
|
446
542
|
let project: TaskCard['project'];
|
|
447
543
|
let targetIsGit: boolean | null = null;
|
|
448
544
|
if (opts.project) {
|
|
@@ -467,19 +563,25 @@ export async function handTask(opts: HandTaskOptions): Promise<HandTaskResult> {
|
|
|
467
563
|
...(opts.seat ? { seat: opts.seat } : {}),
|
|
468
564
|
...(opts.checklistId ? { checklistId: opts.checklistId } : {}),
|
|
469
565
|
...(opts.completion ? { completion: opts.completion } : {}),
|
|
566
|
+
...(selected.composition ? { composition: selected.composition, graphSelectedBy: 'completion' as const } : {}),
|
|
567
|
+
unitsResolved: selected.unitsResolved,
|
|
568
|
+
missingUnits: selected.missingUnits,
|
|
470
569
|
...(project ? { project } : {}),
|
|
471
570
|
...(opts.goal ? { goal: opts.goal } : {}),
|
|
472
571
|
...(opts.milestone ? { milestone: opts.milestone } : {}),
|
|
473
572
|
...(opts.taLand === true ? { taLand: true as const } : {}),
|
|
474
573
|
...(childLlm ? { childLlm } : {}),
|
|
475
574
|
createdAt: now.toISOString(),
|
|
476
|
-
status: 'handed',
|
|
477
|
-
history: [
|
|
575
|
+
status: selectionFailure ? 'failed' : 'handed',
|
|
576
|
+
history: selectionFailure ? [{ at: now.toISOString(), event: !selected.composition ? 'composition-missing'
|
|
577
|
+
: selected.error ? 'composition-invalid' : 'composition-launcher-unavailable', detail: selectionFailure }] : [],
|
|
478
578
|
...(opts.mission ? { mission: opts.mission, after: [...(opts.after ?? [])] } : {}),
|
|
479
|
-
...(opts.cardFields
|
|
579
|
+
...(opts.cardFields?.pr !== undefined ? { pr: opts.cardFields.pr } : {}),
|
|
580
|
+
...(opts.cardFields?.goalId !== undefined ? { goalId: opts.cardFields.goalId } : {}),
|
|
581
|
+
...(opts.cardFields?.handed !== undefined ? { handed: opts.cardFields.handed } : {}),
|
|
480
582
|
};
|
|
481
|
-
const move = nextMoveFor(card, opts.landed);
|
|
482
|
-
if (move.kind !== 'launch') throw new Error(`발사할 수 없는 과제다 — ${move.reason}`);
|
|
583
|
+
const move = selectionFailure ? undefined : nextMoveFor(card, opts.landed);
|
|
584
|
+
if (move && move.kind !== 'launch') throw new Error(`발사할 수 없는 과제다 — ${move.reason}`);
|
|
483
585
|
const guard = opts.writeGuard;
|
|
484
586
|
const writeCard = (target: string, next: TaskCard): void => {
|
|
485
587
|
if (!guard) { writeTaskCards(target, [next]); return; }
|
|
@@ -495,6 +597,13 @@ export async function handTask(opts: HandTaskOptions): Promise<HandTaskResult> {
|
|
|
495
597
|
if (!owned.ok) throw new TaskCardSupersededError(next.id);
|
|
496
598
|
};
|
|
497
599
|
writeCard(path, card);
|
|
600
|
+
try { debug.log('task-agent.graph-select', !selected.composition ? 'composition-missing' : selected.error ? 'composition-invalid' : 'composition-selected', {
|
|
601
|
+
completion, file: selected.composition?.file ?? `graphs/work/${completion}.yaml`,
|
|
602
|
+
unitsResolved: selected.unitsResolved, missingUnits: selected.missingUnits,
|
|
603
|
+
...(selected.error ? { error: selected.error } : {}),
|
|
604
|
+
}); } catch { /* fail-soft */ }
|
|
605
|
+
if (selectionFailure) throw new Error(`${selectionFailure} (카드 ${id})`);
|
|
606
|
+
if (!move || move.kind !== 'launch') throw new Error('조합 선택 뒤 발사 수가 없다');
|
|
498
607
|
try { debug.log('task-agent', 'handed', { taskId: id, seat: card.seat ?? null, checklistId: card.checklistId ?? null, mode, projectId: project?.id ?? null, targetIsGit, taLand: card.taLand === true }); } catch { /* fail-soft */ }
|
|
499
608
|
if (childLlm) {
|
|
500
609
|
try { debug.log('task-agent', 'child-llm', { taskId: id, input: opts.childLlm, provider: childLlm.provider, model: childLlm.model, effort: childLlm.effort ?? null, mode }); } catch { /* fail-soft */ }
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/** Pod repair result: a commit based on the exact PR head, returned without opening a PR. */
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
6
|
+
|
|
7
|
+
export const REPAIR_REF = 'refs/elanous/repair/result';
|
|
8
|
+
export const REPAIR_BUNDLE_MAX_BYTES = 5 * 1024 * 1024;
|
|
9
|
+
const SHA40 = /^[0-9a-f]{40}$/;
|
|
10
|
+
|
|
11
|
+
export interface PodRepairResult {
|
|
12
|
+
pr: number;
|
|
13
|
+
baseHead: string;
|
|
14
|
+
runId: string;
|
|
15
|
+
commit: string;
|
|
16
|
+
bundleSha256: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Host-side receipt gate. A missing, mismatched, or truncated return is never a repair result. */
|
|
20
|
+
export function readPodRepairArtifact(artifactDir: string, expected: { pr: number; baseHead: string; runId?: string }): PodRepairResult {
|
|
21
|
+
const result = JSON.parse(readFileSync(join(artifactDir, 'result.json'), 'utf8')) as Partial<PodRepairResult>;
|
|
22
|
+
if (result.pr !== expected.pr || result.baseHead !== expected.baseHead || (expected.runId && result.runId !== expected.runId)
|
|
23
|
+
|| !result.runId || !SHA40.test(result.commit ?? '') || !/^[0-9a-f]{64}$/.test(result.bundleSha256 ?? '')) {
|
|
24
|
+
throw new Error('repair result mismatch');
|
|
25
|
+
}
|
|
26
|
+
const bundle = readFileSync(join(artifactDir, 'commit.bundle'));
|
|
27
|
+
if (bundle.length > REPAIR_BUNDLE_MAX_BYTES) throw new Error('repair bundle too large');
|
|
28
|
+
if (createHash('sha256').update(bundle).digest('hex') !== result.bundleSha256) throw new Error('repair bundle checksum mismatch');
|
|
29
|
+
return result as PodRepairResult;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function git(cwd: string, ...args: string[]): string {
|
|
33
|
+
return execFileSync('git', ['-C', cwd, ...args], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function completedWorktree(output: string): { path: string; runId: string } {
|
|
37
|
+
for (const line of output.trimEnd().split(/\r?\n/).reverse()) {
|
|
38
|
+
if (!line.startsWith('{')) continue;
|
|
39
|
+
let value: Record<string, unknown>;
|
|
40
|
+
try { value = JSON.parse(line) as Record<string, unknown>; } catch { continue; }
|
|
41
|
+
if (value.kind === 'self' && value.result && typeof value.result === 'object') value = value.result as Record<string, unknown>;
|
|
42
|
+
if (value.stage !== 'worktree-completed' || value.ok !== true || typeof value.worktreePath !== 'string'
|
|
43
|
+
|| typeof value.runId !== 'string' || !value.runId)
|
|
44
|
+
throw new Error('repair child final result is not a successful worktree-only result');
|
|
45
|
+
return { path: value.worktreePath, runId: value.runId };
|
|
46
|
+
}
|
|
47
|
+
throw new Error('repair child did not return a successful worktree-only result');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Called in the Pod after the child exits successfully and before artifact export. */
|
|
51
|
+
export function writePodRepairCommit(input: { pr: number; baseHead: string; runId: string; output: string; repo: string; outbox: string }): PodRepairResult {
|
|
52
|
+
if (!Number.isSafeInteger(input.pr) || input.pr < 1 || !SHA40.test(input.baseHead)) throw new Error('invalid repair PR or base head');
|
|
53
|
+
if (!input.runId.trim()) throw new Error('repair parent run id missing');
|
|
54
|
+
const { path, runId: childRunId } = completedWorktree(input.output);
|
|
55
|
+
if (!isAbsolute(path)) throw new Error('repair worktree path is not absolute');
|
|
56
|
+
const worktree = realpathSync(path);
|
|
57
|
+
const repo = realpathSync(input.repo);
|
|
58
|
+
if (worktree === repo) throw new Error('repair result must be a separate worktree');
|
|
59
|
+
if (git(worktree, 'rev-parse', '--is-inside-work-tree') !== 'true') throw new Error('repair result is not a git worktree');
|
|
60
|
+
const commonDir = (cwd: string) => realpathSync(resolve(cwd, git(cwd, 'rev-parse', '--git-common-dir')));
|
|
61
|
+
if (commonDir(worktree) !== commonDir(repo)) throw new Error('repair worktree belongs to another repository');
|
|
62
|
+
if (git(worktree, 'config', '--worktree', '--get', 'elanous.harness.owner') !== `dev:${childRunId}`
|
|
63
|
+
|| git(worktree, 'config', '--worktree', '--get', 'elanous.harness.command') !== 'elanous dev')
|
|
64
|
+
throw new Error('repair worktree is not owned by the completed child run');
|
|
65
|
+
if (git(repo, 'rev-parse', 'HEAD') !== input.baseHead) throw new Error('Pod source head changed before repair export');
|
|
66
|
+
git(worktree, 'cat-file', '-e', `${input.baseHead}^{commit}`);
|
|
67
|
+
execFileSync('git', ['-C', worktree, 'add', '-A', '--', '.'], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
68
|
+
const staged = git(worktree, 'diff', '--cached', '--name-only');
|
|
69
|
+
if (staged) git(worktree, 'commit', '-m', `repair: PR #${input.pr} from ${input.baseHead.slice(0, 12)}`);
|
|
70
|
+
const commit = git(worktree, 'rev-parse', 'HEAD');
|
|
71
|
+
if (!SHA40.test(commit) || commit === input.baseHead) throw new Error('repair made no new commit');
|
|
72
|
+
execFileSync('git', ['-C', worktree, 'merge-base', '--is-ancestor', input.baseHead, commit], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
73
|
+
git(worktree, 'update-ref', REPAIR_REF, commit);
|
|
74
|
+
const dir = join(input.outbox, 'repair');
|
|
75
|
+
mkdirSync(dir, { recursive: true });
|
|
76
|
+
const bundle = join(dir, 'commit.bundle');
|
|
77
|
+
execFileSync('git', ['-C', worktree, 'bundle', 'create', bundle, REPAIR_REF, `^${input.baseHead}`], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
78
|
+
const size = statSync(bundle).size;
|
|
79
|
+
if (size > REPAIR_BUNDLE_MAX_BYTES) throw new Error(`repair bundle too large: ${size} > ${REPAIR_BUNDLE_MAX_BYTES}`);
|
|
80
|
+
const bundleSha256 = createHash('sha256').update(readFileSync(bundle)).digest('hex');
|
|
81
|
+
const result = { pr: input.pr, baseHead: input.baseHead, runId: input.runId, commit, bundleSha256 };
|
|
82
|
+
writeFileSync(join(dir, 'result.json'), JSON.stringify(result) + '\n', { flag: 'wx', mode: 0o600 });
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (import.meta.main) {
|
|
87
|
+
try {
|
|
88
|
+
const [pr, baseHead, outputPath, repo, outbox] = process.argv.slice(2);
|
|
89
|
+
if (!pr || !baseHead || !outputPath || !repo || !outbox) throw new Error('usage: pod-repair-commit <pr> <base-head> <child-output> <repo> <outbox>');
|
|
90
|
+
const result = writePodRepairCommit({ pr: Number(pr), baseHead, runId: process.env.ELANOUS_PARENT_RUN_ID ?? '', output: readFileSync(outputPath, 'utf8'), repo, outbox });
|
|
91
|
+
console.log(`ELANOUS_POD_REPAIR_COMMIT ${result.pr} ${result.baseHead} ${result.commit}`);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error(`ELANOUS_POD_REPAIR_FAILED ${error instanceof Error ? error.message : String(error)}`);
|
|
94
|
+
process.exitCode = 1;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -19,6 +19,7 @@ import { coalescedInstallationCredential } from '../../auth/github-app-token.js'
|
|
|
19
19
|
import { POD_CREDENTIAL_TOKEN_ENV, POD_CREDENTIAL_URL_ENV } from '../../grok/credential.js';
|
|
20
20
|
import { collectPodLedgers, createPodLedgerFollower } from './pod-ledger-collect.js';
|
|
21
21
|
import { collectPodArtifacts, podNoResultDiagnostic } from './pod-artifact-return.js';
|
|
22
|
+
import { readPodRepairArtifact } from './pod-repair-commit.js';
|
|
22
23
|
import { isValidAccountName, resolveCodexAccount } from '../../oauth/codex-account.js';
|
|
23
24
|
import { effectiveInstanceRoot } from '../../instance/resolve.js';
|
|
24
25
|
import { controlInboxEnv } from '../../harness/control-inbox.js';
|
|
@@ -27,7 +28,7 @@ import { mintRunId, normalizeRunId } from '../../harness/harness-space.js';
|
|
|
27
28
|
import { TA_LAND_ENV } from '../../task-agent/task-hand.js';
|
|
28
29
|
import { appendRunLedgerEntry, loadRunLedger, runLedgerDir, runLedgerPath } from '../../self-implement/run-ledger.js';
|
|
29
30
|
import type { GoalExecutionRecord } from '../../self-implement/orchestrator.js';
|
|
30
|
-
import { runHostRegate, type HostRegateResult } from '../../self-implement/host-regate.js';
|
|
31
|
+
import { hostRepositoryUnavailable, runHostRegate, type HostRegateResult } from '../../self-implement/host-regate.js';
|
|
31
32
|
import { resolvePodHostAcpReview, runPodHostAcpReview, type PodHostAcpReviewOutcome } from './pod-host-acp-review.js';
|
|
32
33
|
import { REVIEW_DEFERRED_TO_HOST_ENV, REVIEW_DEFERRED_TO_HOST_MERGE_REASON } from '../../self-implement/review-deferred-to-host.js';
|
|
33
34
|
import { classifyPodTerminal, applyPodTerminal, podTerminalPrBody, type PrTerminalRecord, type DraftResidueContext } from '../../self-dev/draft-residue.js';
|
|
@@ -137,6 +138,8 @@ export interface PodSpawnOptions {
|
|
|
137
138
|
authorConfig?: () => UserConfig;
|
|
138
139
|
/** 원천 — 없으면 종전 `git clone --depth 50`. bundle 이면 apply 뒤 kubectl cp 로 싣는다. */
|
|
139
140
|
source?: PodSource;
|
|
141
|
+
/** Existing PR repair: pin the source head and export a commit bundle instead of opening a PR. */
|
|
142
|
+
repairCommitOnly?: { pr: number; baseHead: string };
|
|
140
143
|
/** 호스트 환경에서 읽어 Pod env 로 넣을 키 이름(예: OPENROUTER_API_KEY · ANTHROPIC_API_KEY) — 벤치마크 과금 경로. */
|
|
141
144
|
passEnv?: readonly string[];
|
|
142
145
|
/** 호스트가 재발사를 소유하면 Pod 자식의 자체 감독을 끈다. 독립 호출 기본 true; 실제 CLI 호스트는 자기 감독 상태를 명시한다. */
|
|
@@ -953,7 +956,10 @@ export function podSigtermTrapScript(seqFile: string = POD_USAGE_PARTIAL_SEQ_FIL
|
|
|
953
956
|
return `trap '{ [ -n "$si_pid" ] && kill "$si_pid" 2>/dev/null || true; [ -n "\${usage_partial_pid:-}" ] && kill "$usage_partial_pid" 2>/dev/null || true; si_seq=$(cat ${seqFile} 2>/dev/null || true); case "$si_seq" in ""|*[!0-9]*) si_seq=0;; esac; si_seq=$((10#$si_seq + 1)); { [ -f scripts/usage-rollup.ts ] && bun scripts/usage-rollup.ts --since 12h --partial --seq "$si_seq"; } & si_rollup_pid=$!; for si_i in 1 2 3 4 5 6 7 8; do kill -0 "$si_rollup_pid" 2>/dev/null || break; sleep 1; done; kill "$si_rollup_pid" 2>/dev/null || true; wait "$si_rollup_pid" 2>/dev/null || true; exit 143; }' TERM`;
|
|
954
957
|
}
|
|
955
958
|
|
|
956
|
-
export function podJobManifest(o: { name: string; namespace: string; image: string; workIdentity?: PodWorkIdentity; /** 레지스트리 이미지면 IfNotPresent(노드가 pull) · 반입 이미지면 Never. */ imagePullPolicy?: 'Never' | 'IfNotPresent'; repoUrl: string; source?: PodSource; hostMirror?: string; args: readonly string[]; passEnv: readonly string[]; deadlineSeconds: number; runId?: string; parentRunId?: string; armEnv?: Readonly<Record<string, string>>; hostId?: string; imageCommit?: string | null; skillEnvs?: readonly string[]; memoryLimit?: string; memoryRequest?: string; goalDoc?: string; /** AUTHOR-POD2 — run `harness say` on /creds/feature inside the Pod (authoring happens off the host). */ authorSentence?: boolean; authorGrade?: 'full' | 'lite'; grokCredential?: 'subscription' | 'api_key'; codexAccounts?: readonly string[]; appCredential?: boolean; /** Test seam — default POD_GH_STALE_SECONDS. */ githubStaleSeconds?: number; /** Which goal execution and attempt this Job is — a resumed host verifies it before following the Job (POD9). */ execution?: { key: string; attempt: number }; hostLeaseAdmitted?: boolean; /** POD-ROLE-LLM — OpenRouter 자식 Pod 에 리뷰어용 codex 자격을 깐다. */ reviewerCodexAuth?: boolean; /** BEDROCK-PROVIDER ② — bedrock 자식만: 이름 있는 AWS Secret 의 키(secretKeyRef) ⊕ 리전 ⊕ 이그레스 라벨. */ bedrock?: BedrockPodSpec; /** ZAI-POD-CHILD — glm(ZAI) 자식만: 이름 있는 ZAI Secret 의 키(secretKeyRef) ⊕ 이그레스·동시 상한 라벨(Pod ⊕ Job). */ zai?: ZaiPodSpec }): Record<string, unknown> {
|
|
959
|
+
export function podJobManifest(o: { name: string; namespace: string; image: string; workIdentity?: PodWorkIdentity; /** 레지스트리 이미지면 IfNotPresent(노드가 pull) · 반입 이미지면 Never. */ imagePullPolicy?: 'Never' | 'IfNotPresent'; repoUrl: string; source?: PodSource; repairCommitOnly?: { pr: number; baseHead: string }; hostMirror?: string; args: readonly string[]; passEnv: readonly string[]; deadlineSeconds: number; runId?: string; parentRunId?: string; armEnv?: Readonly<Record<string, string>>; hostId?: string; imageCommit?: string | null; skillEnvs?: readonly string[]; memoryLimit?: string; memoryRequest?: string; goalDoc?: string; /** AUTHOR-POD2 — run `harness say` on /creds/feature inside the Pod (authoring happens off the host). */ authorSentence?: boolean; authorGrade?: 'full' | 'lite'; grokCredential?: 'subscription' | 'api_key'; codexAccounts?: readonly string[]; appCredential?: boolean; /** Test seam — default POD_GH_STALE_SECONDS. */ githubStaleSeconds?: number; /** Which goal execution and attempt this Job is — a resumed host verifies it before following the Job (POD9). */ execution?: { key: string; attempt: number }; hostLeaseAdmitted?: boolean; /** POD-ROLE-LLM — OpenRouter 자식 Pod 에 리뷰어용 codex 자격을 깐다. */ reviewerCodexAuth?: boolean; /** BEDROCK-PROVIDER ② — bedrock 자식만: 이름 있는 AWS Secret 의 키(secretKeyRef) ⊕ 리전 ⊕ 이그레스 라벨. */ bedrock?: BedrockPodSpec; /** ZAI-POD-CHILD — glm(ZAI) 자식만: 이름 있는 ZAI Secret 의 키(secretKeyRef) ⊕ 이그레스·동시 상한 라벨(Pod ⊕ Job). */ zai?: ZaiPodSpec }): Record<string, unknown> {
|
|
960
|
+
if (o.repairCommitOnly && (o.source?.kind !== 'pr' || o.source.number !== o.repairCommitOnly.pr || !/^[0-9a-f]{40}$/.test(o.repairCommitOnly.baseHead) || o.args.includes('--open-pr') || o.args.includes('--merge-by-host') || o.args.includes('--auto-merge'))) {
|
|
961
|
+
throw new Error('repair commit-only requires matching PR source and no PR/merge flags');
|
|
962
|
+
}
|
|
957
963
|
const quoted = o.args.map((a) => `'${a.replace(/'/g, `'\\''`)}'`).join(' ');
|
|
958
964
|
const childProviderIndex = o.args.indexOf('--child-llm-provider');
|
|
959
965
|
const childModelIndex = o.args.indexOf('--child-llm-model');
|
|
@@ -962,7 +968,7 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
|
|
|
962
968
|
// POD-ANTHROPIC-PROVIDER — anthropic 자식은 키(env · secretKeyRef)만 쓴다. codex·grok 자격 파일을 깔지 않는다.
|
|
963
969
|
const anthropicChild = childProviderIndex >= 0 && o.args[childProviderIndex + 1] === 'anthropic';
|
|
964
970
|
const goalPath = o.goalDoc ? `'${(o.goalDoc.startsWith('-') ? `./${o.goalDoc}` : o.goalDoc).replace(/'/g, `'\\''`)}'` : undefined;
|
|
965
|
-
const delegated = Boolean(o.goalDoc) || o.authorSentence === true;
|
|
971
|
+
const delegated = !o.repairCommitOnly && (Boolean(o.goalDoc) || o.authorSentence === true);
|
|
966
972
|
const askBaseIndex = delegated ? o.args.indexOf('--base') : -1;
|
|
967
973
|
const quotedAsk = (askBaseIndex >= 0 && o.args[askBaseIndex + 1]
|
|
968
974
|
? ` '--base' '${o.args[askBaseIndex + 1]!.replace(/'/g, `'\\''`)}'`
|
|
@@ -997,12 +1003,13 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
|
|
|
997
1003
|
podGitPushGuardScript(),
|
|
998
1004
|
'curl -s -m 3 -o /dev/null http://host.orb.internal:31415/health && { echo "[pod] ISOLATION FAIL"; exit 3; }',
|
|
999
1005
|
podSourceScript(o.source ?? { kind: 'default' }, o.repoUrl),
|
|
1006
|
+
...(o.repairCommitOnly ? [`[ "$(git rev-parse HEAD)" = '${o.repairCommitOnly.baseHead}' ] || { echo 'ELANOUS_POD_REPAIR_FAILED source-head-mismatch'; exit 8; }`] : []),
|
|
1000
1007
|
...(goalPath ? [
|
|
1001
1008
|
`mkdir -p -- "$(dirname -- ${goalPath})" && cp -- /creds/goal-doc ${goalPath} || exit 6`,
|
|
1002
1009
|
] : []),
|
|
1003
1010
|
// Only fixed command shapes leave the Pod; arbitrary argv (including shell -c text) is never emitted.
|
|
1004
1011
|
`(while :; do { mem=$(if [ -r /sys/fs/cgroup/memory.current ]; then cat /sys/fs/cgroup/memory.current; elif [ -r /sys/fs/cgroup/memory/memory.usage_in_bytes ]; then cat /sys/fs/cgroup/memory/memory.usage_in_bytes; else printf -- -; fi); top=$(ps -eo rss=,comm=,args= --sort=-rss | head -5 | awk 'function encode(s) { gsub(/%/, "%25", s); gsub(/:/, "%3A", s); gsub(/ /, "%20", s); gsub(/\\t/, "%09", s); return s } { rss=$1; name=$2; sub(/^[[:space:]]*[0-9]+[[:space:]]+[^[:space:]]+[[:space:]]*/, ""); n=split($0, a, /[[:space:]]+/); cmd="<redacted>"; if (name=="sleep" && a[2]=="30") cmd="sleep 30" (n>2 ? " <redacted>" : ""); else if (name=="bun") { cmd="bun <redacted>"; if (a[2]=="test") cmd="bun test <redacted>"; else if (a[2]=="run") cmd="bun run <redacted>"; else if ((a[2]=="x" || a[2]=="exec") && a[3]=="tsc") cmd="bun x tsc <redacted>" } else if (name=="tsc") cmd="tsc <redacted>"; else if (name=="elanous") { cmd="elanous <redacted>"; if (a[2]=="self") cmd="elanous self <redacted>"; else if (a[2]=="harness") cmd="elanous harness <redacted>" } else if (name=="node") cmd="node <redacted>"; if (name=="sleep" && cmd=="<redacted>") cmd="sleep <redacted>"; else if (cmd=="<redacted>" && name!="bash" && name!="sh") name="other"; printf " %s:%s:%s", rss, encode(name), encode(substr(cmd,1,120)) }'); printf "ELANOUS_MEM %s %s%s\\n" "$(date +%s)" "$mem" "$top"; } || true; sleep 15 || break; done) & mem_sampler_pid=$!`,
|
|
1005
|
-
...(o.appCredential ? [podGithubWatchdogScript(o.githubStaleSeconds ?? POD_GH_STALE_SECONDS)] : []),
|
|
1012
|
+
...(o.appCredential && !o.repairCommitOnly ? [podGithubWatchdogScript(o.githubStaleSeconds ?? POD_GH_STALE_SECONDS)] : []),
|
|
1006
1013
|
// METER-POD-ROLLUP-GAP — 부분 롤업 표본기: 최종 줄까지 못 사는 Pod 도 N 분마다(기본 5 · ELANOUS_USAGE_PARTIAL_MINUTES) PARTIAL 줄을 하나씩 남긴다.
|
|
1007
1014
|
`(usage_partial_seq=0; while :; do { usage_partial_seq=$((usage_partial_seq+1)); printf '%s\\n' "$usage_partial_seq" > ${POD_USAGE_PARTIAL_SEQ_FILE} 2>/dev/null || true; [ -f scripts/usage-rollup.ts ] && bun scripts/usage-rollup.ts --since 12h --partial --seq "$usage_partial_seq" || true; } || true; usage_partial_sleep=$(( \${ELANOUS_USAGE_PARTIAL_MINUTES:-5} * 60 )); sleep "$usage_partial_sleep" || break; done) & usage_partial_pid=$!`,
|
|
1008
1015
|
// ⭐ MF-d3b5a70f — si_pid 초기화 + trap: 메인 명령을 아래에서 `& si_pid=$!` 로 띄우고 `wait "$si_pid"` 로
|
|
@@ -1013,7 +1020,9 @@ export function podJobManifest(o: { name: string; namespace: string; image: stri
|
|
|
1013
1020
|
// 마지막 줄 JSON 이 «맨 끝»이어야 한다(parseSelfImplementJson) — rollup 은 그 앞에.
|
|
1014
1021
|
// ⛔ `--author-grade` 는 Pod 안 `elanous`(이미지에 깔린 판)가 모를 수 있어 넘기지 않는다 — 10-06 «unknown option» 즉사(#24445 되돌림).
|
|
1015
1022
|
// AUTHOR-LITE2-POD — 등급은 환경변수 `ELANOUS_AUTHOR_GRADE` 로 싣는다(옛 Pod elanous 는 모르는 환경변수를 무시하고 full 로 돈다).
|
|
1016
|
-
o.
|
|
1023
|
+
o.repairCommitOnly
|
|
1024
|
+
? `export ${OLD_DOOR_STAMP_ENV}=self-implement; elanous self implement "$(cat /creds/feature)" --json --no-open-pr ${quoted} > /tmp/si.out 2>&1 & si_pid=$!; wait "$si_pid"; rc=$?; if [ "$rc" -eq 0 ]; then elanous self repair-commit-export '${o.repairCommitOnly.pr}' '${o.repairCommitOnly.baseHead}' /tmp/si.out "$PWD" "$HOME/outbox" || rc=8; fi`
|
|
1025
|
+
: o.authorSentence
|
|
1017
1026
|
? `echo "ELANOUS_AUTHOR_ON_POD started host=$(hostname) at=$(date -u +%Y-%m-%dT%H:%M:%SZ)"; author_t0=$(date +%s); ${o.authorGrade === 'lite' || o.authorGrade === 'full' ? `ELANOUS_AUTHOR_GRADE=${o.authorGrade} ` : ''}elanous harness say --substrate local --json${quotedAsk}${quotedChildLlm} -- "$(cat /creds/feature)" > /tmp/si.out 2>&1 & si_pid=$!; wait "$si_pid"; rc=$?; echo "ELANOUS_AUTHOR_ON_POD finished host=$(hostname) rc=$rc seconds=$(( $(date +%s) - author_t0 ))"; ${POD_RESULT_FLATTEN}`
|
|
1018
1027
|
: goalPath
|
|
1019
1028
|
? `elanous harness ask ${goalPath} --json${quotedAsk}${quotedChildLlm} > /tmp/si.out 2>&1 & si_pid=$!; wait "$si_pid"; rc=$?; ${POD_RESULT_FLATTEN}`
|
|
@@ -1074,12 +1083,12 @@ for ledger in "\${ELANOUS_STATE_DIR:-$HOME/.elanous}"/run-ledger/*.jsonl; do
|
|
|
1074
1083
|
done
|
|
1075
1084
|
if [ "$found" -eq 0 ]; then echo ELANOUS_RUN_LEDGER_NONE; fi`,
|
|
1076
1085
|
'kill "$mem_sampler_pid" 2>/dev/null || true',
|
|
1077
|
-
...(o.appCredential ? ['kill "$gh_watch_pid" 2>/dev/null || true'] : []),
|
|
1086
|
+
...(o.appCredential && !o.repairCommitOnly ? ['kill "$gh_watch_pid" 2>/dev/null || true'] : []),
|
|
1078
1087
|
'[ -n "${usage_partial_pid:-}" ] && kill "$usage_partial_pid" 2>/dev/null || true',
|
|
1079
1088
|
'wait "$mem_sampler_pid" 2>/dev/null || true',
|
|
1080
1089
|
'[ -n "${usage_partial_pid:-}" ] && wait "$usage_partial_pid" 2>/dev/null || true',
|
|
1081
1090
|
'tail -n 1 /tmp/si.out',
|
|
1082
|
-
podSalvageScript(),
|
|
1091
|
+
...(o.repairCommitOnly ? [] : [podSalvageScript()]),
|
|
1083
1092
|
'exit $rc',
|
|
1084
1093
|
].join('\n');
|
|
1085
1094
|
return {
|
|
@@ -1149,6 +1158,9 @@ function defaultGhComment(prNumber: number, body: string): void {
|
|
|
1149
1158
|
}
|
|
1150
1159
|
|
|
1151
1160
|
export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplementJobSpawn {
|
|
1161
|
+
if (options.repairCommitOnly && (options.source?.kind !== 'pr' || options.source.number !== options.repairCommitOnly.pr || !/^[0-9a-f]{40}$/.test(options.repairCommitOnly.baseHead))) {
|
|
1162
|
+
throw new Error('repair commit-only requires a matching PR source and exact head');
|
|
1163
|
+
}
|
|
1152
1164
|
const baseKubectl = options.kubectl ?? defaultKubectl;
|
|
1153
1165
|
const sleep = options.sleep ?? ((ms: number) => new Promise<void>((r) => setTimeout(r, ms)));
|
|
1154
1166
|
const namespace = options.namespace ?? 'elanous-test';
|
|
@@ -1160,6 +1172,9 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1160
1172
|
if (hostMirror && !isAbsolute(hostMirror)) throw new Error('pod.hostMirror must be an absolute directory path');
|
|
1161
1173
|
const hostSupervised = options.hostSupervised ?? true;
|
|
1162
1174
|
return (input) => {
|
|
1175
|
+
if (options.repairCommitOnly && (input.autoMerge || input.openPr || input.base !== `pr-${options.repairCommitOnly.pr}`)) {
|
|
1176
|
+
throw new Error('repair commit-only refuses auto merge, new PR, or a different base');
|
|
1177
|
+
}
|
|
1163
1178
|
const name = podJobName(input.spaceId);
|
|
1164
1179
|
let parentGoal: string | undefined;
|
|
1165
1180
|
let parentGoalUnreadable = false;
|
|
@@ -1212,6 +1227,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1212
1227
|
const childRunId = mintRunId();
|
|
1213
1228
|
const executionKey = createHash('sha256').update(JSON.stringify({ spaceId: input.spaceId, feature: input.feature, base: input.base, autoMerge: input.autoMerge, autoReview: input.autoReview, openPr: input.openPr, draft: input.draft })).digest('hex');
|
|
1214
1229
|
const parentRunId = env.ELANOUS_RUN_ID?.trim();
|
|
1230
|
+
if (options.repairCommitOnly && !parentRunId) throw new Error('repair commit-only requires a parent run id');
|
|
1215
1231
|
// Standalone callers journal retries without altering the existing Pod manifest.
|
|
1216
1232
|
const ledgerRunId = parentRunId || `run-${executionKey.slice(0, 8)}-${executionKey.slice(8, 12)}-${executionKey.slice(12, 16)}-${executionKey.slice(16, 20)}-${executionKey.slice(20, 32)}`;
|
|
1217
1233
|
const address = `self-impl:${input.spaceId}`;
|
|
@@ -1237,7 +1253,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1237
1253
|
...(input.autoReview ? ['--auto-review'] : []),
|
|
1238
1254
|
// ⭐ 계약이 미래 노드를 바꾼다(첫 규칙): Pod 는 끝나면 사라지므로 완료 하한 = PR. 작업 트리로만 끝나면 결과가 Pod 와 함께 없어진다.
|
|
1239
1255
|
// ⚠️ 임시 안전망(🅢 2026-09-26) — 🅣 의 «종결 하한 오버레이»(그래프 쪽)가 서면 이 줄을 지우고 그것으로 바꾼다(채널 합의).
|
|
1240
|
-
...(input.openPr || (!input.autoMerge && completionFloorFor({ substrate: 'pod' }) === 'pr') ? ['--open-pr'] : []),
|
|
1256
|
+
...(!options.repairCommitOnly && (input.openPr || (!input.autoMerge && completionFloorFor({ substrate: 'pod' }) === 'pr')) ? ['--open-pr'] : []),
|
|
1241
1257
|
...(input.draft === false ? ['--no-draft'] : []),
|
|
1242
1258
|
...(hostSupervised ? ['--no-supervise'] : []),
|
|
1243
1259
|
...(options.extraArgs ?? []),
|
|
@@ -1327,7 +1343,8 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1327
1343
|
if (unfitDone) return unfitDone;
|
|
1328
1344
|
// HARNESS-FULL-GRAPH — Pod 입장(입장 관문 ⊕ 멤버 슬롯)은 그래프 노드 pool-admit 이다.
|
|
1329
1345
|
// 2판: decompose →(substrate=pod) pool-admit 간선은 «여기»(Pod 입장 직전 · 실제 단계 경계)에서 남긴다.
|
|
1330
|
-
routeJourneyEdge('
|
|
1346
|
+
if (options.repairCommitOnly) routeJourneyEdge('dispatch', 'pr-source', { source: 'pr', substrate: 'pod' });
|
|
1347
|
+
else routeJourneyEdge('decompose', 'pod', { substrate: 'pod' });
|
|
1331
1348
|
enterJourneyNode('pool-admit', { provenance: 'self-implement-pod', data: { spaceId: input.spaceId } });
|
|
1332
1349
|
// ⛔ 감싸는 함수(async 래퍼)를 쓰지 않는다 — 마이크로태스크가 늘면 Job apply 순서가 밀린다(시험이 잡았다). 같은 자리 try/catch 로.
|
|
1333
1350
|
const podAdmitError = (error: unknown): void => {
|
|
@@ -1354,6 +1371,8 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1354
1371
|
try {
|
|
1355
1372
|
if (!context) return { exitCode: 1, output: '', error: { code: 'pod-context', message: 'Pod Job context를 확인할 수 없다' } };
|
|
1356
1373
|
const cleanupSecret = () => { kubectl(['-n', namespace, 'delete', 'secret', `${name}-creds`, '--ignore-not-found']); };
|
|
1374
|
+
let repairResultAnnounced = false;
|
|
1375
|
+
let repairArtifactReady = false;
|
|
1357
1376
|
const collectFullLogs = (unavailableEvent: string, replace?: ReadonlySet<string>, onEvent?: (event: string, data: Record<string, unknown>) => void, childId?: string): { status: 'ok' | 'unavailable' | 'incomplete'; childLedgerIncomplete: boolean; samples: ReturnType<typeof parseMemSamples> } => {
|
|
1358
1377
|
let full: ReturnType<Kubectl>;
|
|
1359
1378
|
try {
|
|
@@ -1384,6 +1403,20 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1384
1403
|
collectPodArtifacts(full.stdout, { dir: join(effectiveInstanceRoot(), 'pod-artifacts'), job: name, log: record });
|
|
1385
1404
|
}
|
|
1386
1405
|
catch (error) { record('self-implement.pod', 'artifact-collect-incomplete', { job: name, reason: error instanceof Error ? error.message : String(error) }); }
|
|
1406
|
+
if (options.repairCommitOnly) {
|
|
1407
|
+
const artifactDir = join(effectiveInstanceRoot(), 'pod-artifacts', name, 'repair');
|
|
1408
|
+
try {
|
|
1409
|
+
const result = readPodRepairArtifact(artifactDir, { ...options.repairCommitOnly, runId: parentRunId });
|
|
1410
|
+
repairArtifactReady = true;
|
|
1411
|
+
if (!repairResultAnnounced) {
|
|
1412
|
+
console.log(`ELANOUS_POD_REPAIR_RESULT ${JSON.stringify({ job: name, artifactDir, pr: result.pr, baseHead: result.baseHead, runId: result.runId, commit: result.commit, bundleSha256: result.bundleSha256 })}`);
|
|
1413
|
+
repairResultAnnounced = true;
|
|
1414
|
+
}
|
|
1415
|
+
} catch (error) {
|
|
1416
|
+
repairArtifactReady = false;
|
|
1417
|
+
record('self-implement.pod', 'repair-artifact-incomplete', { job: name, reason: error instanceof Error ? error.message : String(error) });
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1387
1420
|
return { status: ledgerIncomplete || artifactIncomplete ? 'incomplete' : 'ok', childLedgerIncomplete, samples: parseMemSamples(full.stdout) };
|
|
1388
1421
|
};
|
|
1389
1422
|
let retryAccount: string | undefined;
|
|
@@ -1839,7 +1872,7 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
1839
1872
|
slotId: env.ELANOUS_POD_SLOT_ID,
|
|
1840
1873
|
work: !goalDoc && env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? 'authoring' : (oomRetried ? retryTier : memoryTier) === 'lite' ? 'lite' : 'harness',
|
|
1841
1874
|
};
|
|
1842
|
-
const job = podJobManifest({ name, namespace, image: jobImage, workIdentity, ...(member?.imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}), repoUrl, ...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), args: jobArgs, passEnv: jobPassEnv, deadlineSeconds: options.deadlineSeconds ?? POD_JOB_DEADLINE_SECONDS, ...(goalDoc ? { goalDoc } : env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? { authorSentence: true, ...(env.ELANOUS_POD_AUTHOR_GRADE === 'lite' || env.ELANOUS_POD_AUTHOR_GRADE === 'full' ? { authorGrade: env.ELANOUS_POD_AUTHOR_GRADE } : {}) } : {}), runId: launchRunId, ...(parentRunId ? { parentRunId } : {}), ...(jobArmEnv ? { armEnv: jobArmEnv } : {}), hostId: resolveHostId(env), skillEnvs: Object.keys(skillEnvs), memoryLimit, memoryRequest, imageCommit: options.imageCommit !== undefined ? options.imageCommit : options.kubectl ? null : podImageFreshness({ image }).imageCommit, ...(grok ? { grokCredential: 'grokAuth' in creds && creds.grokAuth ? 'subscription' as const : 'api_key' as const } : {}), ...(codexAccounts ? { codexAccounts } : {}), ...(hostRefresh ? { appCredential: true } : {}), execution: { key: executionKey, attempt: oomRetried ? 2 : 1 }, hostLeaseAdmitted: !!releaseAdmission, ...(reviewerCredential ? { reviewerCodexAuth: true } : {}), ...(bedrockSpec ? { bedrock: bedrockSpec } : {}), ...(zaiSpec ? { zai: zaiSpec } : {}) }); // kubectl 주입(=시험)이면 docker 를 부르지 않는다
|
|
1875
|
+
const job = podJobManifest({ name, namespace, image: jobImage, workIdentity, ...(member?.imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}), repoUrl, ...(options.source ? { source: options.source } : {}), ...(options.repairCommitOnly ? { repairCommitOnly: options.repairCommitOnly } : {}), ...(hostMirror ? { hostMirror } : {}), args: jobArgs, passEnv: jobPassEnv, deadlineSeconds: options.deadlineSeconds ?? POD_JOB_DEADLINE_SECONDS, ...(goalDoc ? { goalDoc } : env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? { authorSentence: true, ...(env.ELANOUS_POD_AUTHOR_GRADE === 'lite' || env.ELANOUS_POD_AUTHOR_GRADE === 'full' ? { authorGrade: env.ELANOUS_POD_AUTHOR_GRADE } : {}) } : {}), runId: launchRunId, ...(parentRunId ? { parentRunId } : {}), ...(jobArmEnv ? { armEnv: jobArmEnv } : {}), hostId: resolveHostId(env), skillEnvs: Object.keys(skillEnvs), memoryLimit, memoryRequest, imageCommit: options.imageCommit !== undefined ? options.imageCommit : options.kubectl ? null : podImageFreshness({ image }).imageCommit, ...(grok ? { grokCredential: 'grokAuth' in creds && creds.grokAuth ? 'subscription' as const : 'api_key' as const } : {}), ...(codexAccounts ? { codexAccounts } : {}), ...(hostRefresh ? { appCredential: true } : {}), execution: { key: executionKey, attempt: oomRetried ? 2 : 1 }, hostLeaseAdmitted: !!releaseAdmission, ...(reviewerCredential ? { reviewerCodexAuth: true } : {}), ...(bedrockSpec ? { bedrock: bedrockSpec } : {}), ...(zaiSpec ? { zai: zaiSpec } : {}) }); // kubectl 주입(=시험)이면 docker 를 부르지 않는다
|
|
1843
1876
|
const carriedBranch = goalDoc ? 'goal-doc' as const : env.ELANOUS_POD_AUTHOR_ON_POD === '1' && !shard ? 'author' as const : undefined;
|
|
1844
1877
|
const carriedChildLlm = carriedBranch ? podDelegatedChildLlm(jobArgs) : undefined;
|
|
1845
1878
|
const a = kubectl(['apply', '-f', '-'], JSON.stringify(job));
|
|
@@ -2047,6 +2080,9 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2047
2080
|
}
|
|
2048
2081
|
// Pod 안 경로는 호스트에서 쓸 수 없다.
|
|
2049
2082
|
let disposition = parsed ? { ...parsed, worktreePath: undefined } : undefined;
|
|
2083
|
+
if (options.repairCommitOnly && state === 'complete' && !repairArtifactReady) {
|
|
2084
|
+
disposition = { ...disposition, worktreePath: undefined, ok: false, stage: 'repair-artifact-missing', detail: 'Pod repair result or bundle was not collected' };
|
|
2085
|
+
}
|
|
2050
2086
|
if (input.autoMerge && state === 'complete' && disposition?.stage === 'merge-ready') {
|
|
2051
2087
|
const prNumber = disposition.prNumber;
|
|
2052
2088
|
const headCommit = disposition.checkedHeadCommit;
|
|
@@ -2097,13 +2133,35 @@ export function podSelfImplementSpawn(options: PodSpawnOptions = {}): SelfImplem
|
|
|
2097
2133
|
debug.log('harness.host-regate', 'unmeasured', { pr: prNumber ?? null, files: [], os: process.platform, failedStep: 'pod-result' });
|
|
2098
2134
|
regate = { passed: false, failures: [{ step: 'pod-result', detail: 'missing PR number or checked head commit' }], os: process.platform };
|
|
2099
2135
|
} else {
|
|
2136
|
+
let hostRoot: string | undefined;
|
|
2137
|
+
let hostCwd: string | undefined;
|
|
2100
2138
|
try {
|
|
2101
|
-
|
|
2139
|
+
hostCwd = process.cwd();
|
|
2140
|
+
hostRoot = findGitDir(hostCwd)?.root;
|
|
2102
2141
|
if (!hostRoot) throw new Error('host repository unavailable');
|
|
2103
2142
|
regate = await (options.hostRegate ?? runHostRegate)({ prNumber, headCommit, repoRoot: hostRoot, ...(goalFile ? { goalFile } : {}), ...(taLandOwner ? { noMerge: true as const } : {}) });
|
|
2104
2143
|
} catch (error) {
|
|
2105
2144
|
debug.log('harness.host-regate', 'unmeasured', { pr: prNumber, files: [], os: process.platform, failedStep: 'host-regate' });
|
|
2106
|
-
regate = { passed: false, failures: [{ step: 'host-regate', detail: error instanceof Error ? error.message : String(error) }], os: process.platform };
|
|
2145
|
+
regate = { passed: false, ...(taLandOwner ? { status: 'unmeasured' as const } : {}), failures: [{ step: 'host-regate', detail: error instanceof Error ? error.message : String(error) }], os: process.platform };
|
|
2146
|
+
}
|
|
2147
|
+
if (taLandOwner && !hostRoot && hostRepositoryUnavailable(regate)) {
|
|
2148
|
+
let recordedRoot: string | undefined;
|
|
2149
|
+
try {
|
|
2150
|
+
const recorded = loadRunLedger(ledgerRunId, ledgerDir)?.find((entry) => entry.event === 'start')?.data.targetRoot;
|
|
2151
|
+
if (typeof recorded === 'string' && isAbsolute(recorded)) {
|
|
2152
|
+
const foundRoot = findGitDir(recorded)?.root;
|
|
2153
|
+
// The recorded path must itself name the repository root, including symlink aliases.
|
|
2154
|
+
if (foundRoot && realpathSync(foundRoot) === realpathSync(recorded)) recordedRoot = foundRoot;
|
|
2155
|
+
}
|
|
2156
|
+
} catch { /* unreadable ledger or path leaves the original unmeasured result intact */ }
|
|
2157
|
+
if (recordedRoot) {
|
|
2158
|
+
try {
|
|
2159
|
+
regate = await (options.hostRegate ?? runHostRegate)({ prNumber, headCommit, repoRoot: recordedRoot, ...(goalFile ? { goalFile } : {}), noMerge: true });
|
|
2160
|
+
} catch (error) {
|
|
2161
|
+
regate = { passed: false, status: 'unmeasured', failures: [{ step: 'host-regate', detail: error instanceof Error ? error.message : String(error) }], os: process.platform };
|
|
2162
|
+
}
|
|
2163
|
+
debug.log('harness.host-regate', 'regate-retry', { pr: prNumber, from: hostCwd, to: recordedRoot, result: regate.status ?? (regate.passed ? 'passed' : 'failed') });
|
|
2164
|
+
}
|
|
2107
2165
|
}
|
|
2108
2166
|
}
|
|
2109
2167
|
// runHostRegate comments on its own failures; these two paths never reached it.
|