elanous 0.2.25 → 0.2.27
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-37fd16f479f5554a.js → 1454-a9c338f5bb556f42.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/{page-9360b1285b2a346e.js → page-a1ce306364f08da9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-07110da72c45e68f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-d2d76d4a7eae91bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-30007380f2e2a3c1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e5a9302c105725a3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-b93c04b5311c97ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-718a49e86bf25f9c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/{page-44eaad486bc98a45.js → page-20662900d6b4b455.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/control/{page-97fa6cf3d34c7eb9.js → page-f707a509515ac50c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/{page-305cd294e754906e.js → page-6a16a48d1aa6e91f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-fcb06e305f515527.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-6acf5ec4e6c6d1af.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-45889e7070d11126.js → page-39f82acf619cca23.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-16a2e893f83ae4d0.js → page-7c2c8f81b185f9c0.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-ae7aff07e32676bb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-56888bc744e08dac.js → page-482c60c1bc540d0a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-7cb996c1aa08b6f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/{page-952c6ceec6d9de38.js → page-623e08fe4477d606.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/{page-6d0202af568f4c63.js → page-db460cd8aecb8490.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-ff27902d0b11ef4f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-74366ae2693a7d95.js → page-8397a5c6ca569bf4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-4fb6fc34e20c07b7.js → page-1917ea87ea71ab6b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-e9502921f4f01b1f.js → page-25a302273b054fd7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-b6169ea2f09cf287.js → not-found-670510eda8b9443f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-d73c3f1e00d93986.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-65caacf54389081e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4bb7556e2e97a750.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-77e04b4f8762c666.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/{page-b9cc8a10bc6078b4.js → page-15c0a4095baddfba.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{page-3d3fc73dd187ed52.js → page-4eb96fc7241c2456.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-63e272a4cd3b6634.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-9c04dac0831c2bbb.js → page-0d64ff212dc55d03.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-7f096248993fc2d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-04de9ca6d52a3893.js → page-e47fc72049149a38.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-62527677bbc2fdc4.js → page-f7d808e31e005895.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-9a2e09c4a311764c.js → page-5c2b24b5496dad73.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-bfeb35d69e8b24bb.js → page-76cdb531668b5191.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-e2b63345bba1a11f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-b008aff96be56654.js → page-d376e0c83edc041a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-d6db55fe2c04d128.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-87b88f551f421d8b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/{page-6445ac4cbb8dd869.js → page-dab163b338e3b966.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-852170303c6cd755.js → page-08019efef111ed2e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-0260ffc7eda50db1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-97c7361cc2a19319.js → page-980ce6fe36ad95eb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-20162174b77f96cf.js → page-99c4e6c7345a9156.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-8c6901833f897496.js → page-0ab42e0f05823013.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ad9220422d20ba33.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/package.json +1 -1
- package/scripts/ci-typecheck-changed.ts +81 -2
- package/scripts/coord-msg-bridge.ts +209 -0
- package/scripts/coord-post.sh +40 -7
- package/scripts/coord-requests-bridge.ts +10 -5
- package/scripts/expose-rubric-suggest.ts +1 -1
- package/scripts/measure-tsc-affected-roots.ts +10 -1
- package/scripts/release-loop/export-check-node.ts +8 -5
- package/scripts/release-loop/node-verdict.ts +7 -2
- package/src/agent/task-notification.ts +30 -1
- package/src/agent-substrate/review-observation.ts +9 -2
- package/src/agent-substrate/self-review-cli.ts +28 -6
- package/src/boot/daemon-tools/self-implement.ts +72 -4
- package/src/budget/llm-cost.ts +5 -1
- package/src/cli/control-cli.ts +29 -0
- package/src/cli/doctor-cli.ts +90 -8
- package/src/cli/msg-cli.ts +15 -5
- package/src/cli/pr-cli.ts +53 -0
- package/src/cli/pr-land-overlap-evidence.ts +18 -10
- package/src/cli/release-cli.ts +32 -5
- package/src/cli/seat-cli.ts +121 -0
- package/src/cli/self-utterances-cli.ts +140 -0
- package/src/cli/tasks-cli.ts +139 -9
- package/src/context-card/cell.ts +40 -0
- package/src/dashboard/boot-timing.ts +35 -0
- package/src/dashboard/index.ts +86 -24
- package/src/dashboard/input/chat-main-plain-turn-runtime.ts +26 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +6 -0
- package/src/debug/log.ts +1 -1
- package/src/domains/release-tool.ts +29 -12
- package/src/domains/surface-events.ts +16 -0
- package/src/elanous-config-dir.ts +2 -2
- package/src/fleet/host-doctor.ts +73 -2
- package/src/graph-runner/runner.ts +3 -1
- package/src/harness/harness-cli-command.ts +46 -4
- package/src/harness/harness-stop.ts +11 -7
- package/src/index.ts +35 -4
- package/src/instance/current.ts +2 -6
- package/src/instance/resolve.ts +5 -6
- package/src/llm/openai-sampling-fields.ts +89 -0
- package/src/llm/runtime-model-family.ts +61 -0
- package/src/llm/zai-coding-plan.ts +27 -1
- package/src/llm.ts +13 -89
- package/src/machines/machine-ledger.ts +31 -3
- package/src/maturity/cli-maturity.ts +13 -3
- package/src/msg/msg-store.ts +69 -7
- package/src/ops/overview-collect.ts +3 -2
- package/src/ops/overview-owner.ts +29 -0
- package/src/plugin-hooks/shell-hook.ts +17 -2
- package/src/plugin-missions/evaluator-runner.ts +18 -2
- package/src/plugins/install/plugin-install.ts +11 -1
- package/src/prompt-enhance/enhance.ts +11 -0
- package/src/release-loop/checklist.ts +41 -4
- package/src/release-loop/feature-store.ts +10 -9
- package/src/seat-loop/seat-loop.ts +5 -1
- package/src/self-dev/draft-sweep.ts +23 -14
- package/src/self-dev/orchestrate.ts +6 -2
- package/src/self-dev/run-supervisor.ts +35 -1
- package/src/self-implement/goal-author.ts +3 -27
- package/src/self-implement/goal-command-execution.ts +26 -0
- package/src/self-implement/orchestrator.ts +2 -1
- package/src/self-implement/pwa-import-graph.ts +194 -7
- package/src/self-implement/self-implement-runtime.ts +126 -21
- package/src/session/index.ts +14 -4
- package/src/session-runtime/index.ts +55 -11
- package/src/task-agent/cover.ts +28 -9
- package/src/task-agent/live-moves.ts +14 -6
- package/src/task-agent/rejudge-on-head.ts +220 -23
- package/src/task-agent/review-result-capture.ts +64 -1
- package/src/task-agent/shadow.ts +137 -22
- package/src/task-agent/task-hand.ts +18 -2
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +1 -0
- package/src/tool-runtime/tool-search-runtime.ts +25 -0
- package/src/tui.ts +20 -1
- package/src/user-config.ts +3 -59
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f08f6529d09ffae1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-f000d54d258f9ef6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-b12149c51f7d04b1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-22f674e7e4c2c9d5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-8888733e40cea6d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-9717ec6964b92276.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-dea4b795741126fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-35e05d7450461a67.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-b2638013c0bf7dfd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-c44f93a41167536f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-1b810474507f3702.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-6a14333d1b1b3967.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-8c91a9a66c9ec90a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4e634c5b98fd4e93.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ed3070d637850548.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-128458c8d86fbb5f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-cf0c2b0183991d09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-89cb33bced824e4a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-814394dcbae789b1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-6a87325c87f09dd2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-087ceeb975ef272b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-05a4b2c9b91034aa.js +0 -1
- /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → Ev2jivDMTSoEc-5rIXdcy}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → Ev2jivDMTSoEc-5rIXdcy}/_ssgManifest.js +0 -0
|
@@ -456,11 +456,36 @@ function parseGoals(req: SelfImplementRuntimeRequest): string[] | undefined {
|
|
|
456
456
|
return goals;
|
|
457
457
|
}
|
|
458
458
|
|
|
459
|
-
|
|
459
|
+
interface SelfImplementGoalResult {
|
|
460
|
+
goal: string;
|
|
461
|
+
status: SelfDevJobResult['status'];
|
|
462
|
+
runId?: string;
|
|
463
|
+
stage?: string;
|
|
464
|
+
reason?: string;
|
|
465
|
+
parentPrNumber?: number;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function observedGoalResults(results: readonly SelfDevJobResult[]): SelfImplementGoalResult[] {
|
|
469
|
+
return results.map((result, index) => ({
|
|
470
|
+
goal: result.feature || `골 ${index + 1}`,
|
|
471
|
+
status: result.status,
|
|
472
|
+
...(result.runId ? { runId: result.runId } : {}),
|
|
473
|
+
...(result.stage ? { stage: result.stage } : {}),
|
|
474
|
+
...(result.error?.message || result.stopReason || result.blockReason
|
|
475
|
+
? { reason: result.error?.message || result.stopReason || (result.blockReason === 'parent-unlanded'
|
|
476
|
+
? result.parentPrNumber ? `선행 PR #${result.parentPrNumber} 미착지` : '선행 골 미착지'
|
|
477
|
+
: result.blockReason) }
|
|
478
|
+
: {}),
|
|
479
|
+
...(result.parentPrNumber ? { parentPrNumber: result.parentPrNumber } : {}),
|
|
480
|
+
}));
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function formatOrchestrationResults(results: readonly SelfDevJobResult[], goals: readonly SelfImplementGoalResult[]): string {
|
|
460
484
|
const done = results.filter(result => result.status === 'done').length;
|
|
461
485
|
const merged = results.filter(result => result.merged).length;
|
|
462
486
|
const failed = results.filter(result => result.status === 'failed').length;
|
|
463
|
-
|
|
487
|
+
const summary = `[self-implement] parallel ${results.length} goal · done ${done}${merged ? ` (병합 ${merged})` : ''}${failed ? ` · 실패 ${failed}` : ''}`;
|
|
488
|
+
return [summary, ...goals.map(goal => `- ${goal.goal}: ${goal.status} · 런 ${goal.runId ?? '미관측'}${goal.stage ? ` · 단계 ${goal.stage}` : ''}${goal.reason ? ` · 이유 ${goal.reason}` : ''}`)].join('\n');
|
|
464
489
|
}
|
|
465
490
|
|
|
466
491
|
export function resolveDocumentReferences(paths: readonly string[], repositoryRoot = process.cwd()): DocumentReferenceStatus[] {
|
|
@@ -476,7 +501,15 @@ function documentReferenceStatusLines(references: readonly DocumentReferenceStat
|
|
|
476
501
|
|
|
477
502
|
function formatResult(feature: string, r: SelfImplementResult, references: readonly DocumentReferenceStatus[] = []): string {
|
|
478
503
|
const head = `[self-implement] ${r.stage}`;
|
|
479
|
-
const
|
|
504
|
+
const next = r.runId ? `eln self run ${r.runId}` : r.ok ? 'eln self running-runs' : '실패 이유를 확인한 뒤 다시 요청';
|
|
505
|
+
const lines: string[] = [
|
|
506
|
+
r.ok
|
|
507
|
+
? r.runId
|
|
508
|
+
? `하니스가 맡았습니다 · 런 ${r.runId} · 노드 ${r.node || '미관측'} · 다음 조회: ${next} · 이 턴에서 파일을 직접 편집하지 마세요`
|
|
509
|
+
: `하니스 응답을 받았지만 런 식별자는 아직 미관측입니다 · 진행 중 런 조회: ${next} · 일치하는 런이 없으면 요청을 다시 보내세요 · 이 턴에서 파일을 직접 편집하지 마세요`
|
|
510
|
+
: `하니스가 발사되지 않았거나 실패했습니다: ${r.detail ?? r.stage} · 다음: ${next} · 이 턴에서 파일을 직접 편집하지 마세요`,
|
|
511
|
+
head, ...documentReferenceStatusLines(references),
|
|
512
|
+
];
|
|
480
513
|
if (r.branch) lines.push(`branch: ${r.branch}`);
|
|
481
514
|
if (r.worktreePath) lines.push(`worktree: ${r.worktreePath}`);
|
|
482
515
|
if (r.stage === 'pr-opened' && r.prUrl) {
|
|
@@ -494,6 +527,12 @@ function formatResult(feature: string, r: SelfImplementResult, references: reado
|
|
|
494
527
|
export interface SelfImplementRunResult {
|
|
495
528
|
output: string;
|
|
496
529
|
ok: boolean;
|
|
530
|
+
kind: 'launched' | 'observed-only' | 'failed' | 'pending';
|
|
531
|
+
next?: string;
|
|
532
|
+
reason?: string;
|
|
533
|
+
missing?: string[];
|
|
534
|
+
/** 병렬 골마다 실제로 관측된 종료 상태·식별자·이유. */
|
|
535
|
+
goalResults?: SelfImplementGoalResult[];
|
|
497
536
|
// ⛔ 관측 전용 반환에는 **단계가 없다** — 아무 노드도 돌지 않았다. 없는 것을 있는 값으로 적으면
|
|
498
537
|
// «안 돌았다» 와 «어느 단계에서 끝났다» 가 같은 값이 된다(부재와 미지를 같은 값으로 적지 않는다).
|
|
499
538
|
stage?: SelfImplementResult['stage'];
|
|
@@ -506,8 +545,10 @@ export interface SelfImplementRunResult {
|
|
|
506
545
|
observed?: true;
|
|
507
546
|
/** 명시적 Fabric 분해의 상태·기존 3결말·미산출 사유. */
|
|
508
547
|
fabricDecomposition?: FabricDecompositionOutcome;
|
|
509
|
-
/**
|
|
548
|
+
/** 실제 런 결과에서 확인한 식별자. 선할당된 비동기 ID는 반환하지 않는다. */
|
|
510
549
|
runId?: string;
|
|
550
|
+
/** 비동기 요청 조회용 상관 ID. 발사된 런의 증거가 아니다. */
|
|
551
|
+
requestId?: string;
|
|
511
552
|
/** 호출이 블로킹인지 비블로킹인지. 기본 경로에는 없어 기존 반환 모양을 보존한다. */
|
|
512
553
|
invocationMode?: 'blocking' | 'non-blocking';
|
|
513
554
|
}
|
|
@@ -516,6 +557,7 @@ export const selfImplementRuntime: ToolRuntime<SelfImplementRuntimeRequest, Self
|
|
|
516
557
|
id: 'self_implement',
|
|
517
558
|
spec: buildSelfImplementSpec(),
|
|
518
559
|
async run(req, ctx: ToolRuntimeContext): Promise<SelfImplementRunResult> {
|
|
560
|
+
try {
|
|
519
561
|
const hasFeature = req.feature !== undefined;
|
|
520
562
|
const hasGoals = req.goals !== undefined;
|
|
521
563
|
if (hasFeature === hasGoals) {
|
|
@@ -604,7 +646,7 @@ export const selfImplementRuntime: ToolRuntime<SelfImplementRuntimeRequest, Self
|
|
|
604
646
|
if (observeOnly.enabled) {
|
|
605
647
|
const requestSummary = feature ?? goals!.join(' · ');
|
|
606
648
|
debug.log('self-implement', 'runtime.observed', { surface: ctx.surface, ...(feature ? { feature: feature.slice(0, 120) } : { goals: goals!.length }), observeOnly: observeOnly.enabled, observeOnlySource: observeOnly.source });
|
|
607
|
-
return { output:
|
|
649
|
+
return { output: `관측 전용이라 런을 발사하지 않았습니다 · 코드도 안 바꿨습니다 · 이 결과를 구현 실패로 읽고 직접 Write/Edit 하지 마세요. 실제 발사를 원하면 관측 전용 모드를 끈 뒤 다시 요청하세요: ${requestSummary.slice(0, 120)}${documentReferenceStatusLines(documentReferences).length ? `\n${documentReferenceStatusLines(documentReferences).join('\n')}` : ''}`, ok: true, kind: 'observed-only', observed: true, missing: ['runId', 'node'], next: '관측 전용 모드를 끈 뒤 다시 요청' };
|
|
608
650
|
}
|
|
609
651
|
|
|
610
652
|
const allocatedRunId = nonBlocking ? mintRunId() : undefined;
|
|
@@ -637,9 +679,14 @@ export const selfImplementRuntime: ToolRuntime<SelfImplementRuntimeRequest, Self
|
|
|
637
679
|
fabricDecomposition = fabricDecompositionOutcome(fabricResult);
|
|
638
680
|
observeFabricDecomposition(fabricDecomposition, ctx.surface, fabricResult.status === 'decomposed' ? fabricResult.goals.length : undefined);
|
|
639
681
|
if (fabricResult.status !== 'decomposed') {
|
|
682
|
+
const observedOnly = fabricDecomposition.outcome !== 'llm-failed';
|
|
640
683
|
return {
|
|
641
|
-
output: formatFabricDecompositionOutcome(fabricDecomposition)
|
|
642
|
-
ok:
|
|
684
|
+
output: `${formatFabricDecompositionOutcome(fabricDecomposition)}${observedOnly ? '\n관측 전용이라 런을 발사하지 않았습니다 · 코드도 안 바꿨습니다 · 이 결과를 구현 실패로 읽고 직접 Write/Edit 하지 마세요.' : '\n이 턴에서 파일을 직접 편집하지 마세요.'}`,
|
|
685
|
+
ok: observedOnly,
|
|
686
|
+
kind: observedOnly ? 'observed-only' : 'failed',
|
|
687
|
+
next: observedOnly ? '분해 결과를 확인하고 구현 골을 다시 요청' : '분해 실패 이유를 확인한 뒤 다시 요청',
|
|
688
|
+
missing: ['runId', 'node'],
|
|
689
|
+
...(observedOnly ? { observed: true as const } : { reason: formatFabricDecompositionOutcome(fabricDecomposition) }),
|
|
643
690
|
fabricDecomposition,
|
|
644
691
|
};
|
|
645
692
|
}
|
|
@@ -674,10 +721,40 @@ export const selfImplementRuntime: ToolRuntime<SelfImplementRuntimeRequest, Self
|
|
|
674
721
|
...(forwardedSupervise === undefined ? {} : { supervise: forwardedSupervise }),
|
|
675
722
|
...(deliverable === undefined ? {} : { deliverable }),
|
|
676
723
|
});
|
|
677
|
-
const
|
|
724
|
+
const goalResults = observedGoalResults(results);
|
|
725
|
+
const failedGoals = goalResults.filter(result => result.status === 'failed' || result.status === 'cancelled');
|
|
726
|
+
const blockedGoals = goalResults.filter(result => result.status === 'blocked');
|
|
727
|
+
const allRunsIdentified = goalResults.length > 0 && goalResults.every(result => result.runId);
|
|
728
|
+
const kind = goalResults.length === 0 || failedGoals.length > 0 || blockedGoals.length > 0 ? 'failed' : allRunsIdentified ? 'launched' : 'pending';
|
|
729
|
+
const parentBlockedPrNumbers = [...new Set(results.flatMap(result => result.status === 'blocked' && result.blockReason === 'parent-unlanded' && result.parentPrNumber ? [result.parentPrNumber] : []))];
|
|
730
|
+
const parentBlockedWithoutPr = results.some(result => result.status === 'blocked' && result.blockReason === 'parent-unlanded' && !result.parentPrNumber && !result.runId);
|
|
731
|
+
const otherFailureWithoutId = results.some(result => !result.runId && ['failed', 'cancelled', 'blocked'].includes(result.status) && result.blockReason !== 'parent-unlanded');
|
|
732
|
+
const runQueries = [...new Set(goalResults.flatMap(result => result.runId ? [`eln self run ${result.runId}`] : []))];
|
|
733
|
+
const availableQueries = [
|
|
734
|
+
...parentBlockedPrNumbers.map(pr => `eln gh pr view ${pr}`),
|
|
735
|
+
...runQueries,
|
|
736
|
+
];
|
|
737
|
+
const pendingWithoutId = goalResults.some(result => !result.runId && !['failed', 'cancelled', 'blocked'].includes(result.status));
|
|
738
|
+
const next = [
|
|
739
|
+
...availableQueries,
|
|
740
|
+
...(pendingWithoutId ? ['eln self running-runs', '일치하는 런이 없으면 ID 미확정 골을 다시 요청'] : []),
|
|
741
|
+
...(parentBlockedPrNumbers.length ? ['선행 PR 착지 뒤 차단된 골을 재실행하세요'] : []),
|
|
742
|
+
...(parentBlockedWithoutPr ? ['선행 골 상태를 확인한 뒤 해당 골을 재실행하세요'] : []),
|
|
743
|
+
...(otherFailureWithoutId ? ['실패·차단 이유를 확인한 뒤 해당 골을 다시 요청'] : []),
|
|
744
|
+
].join('\n') || '골 결과가 0건이라 상태를 확인한 뒤 다시 요청';
|
|
745
|
+
const reason = goalResults.length === 0
|
|
746
|
+
? '골 결과가 0건이라 상태 미관측'
|
|
747
|
+
: [...failedGoals, ...blockedGoals].map(result => `${result.goal}: ${result.reason ?? '이유 미관측'}`).join('; ');
|
|
678
748
|
return {
|
|
679
|
-
output: formatOrchestrationResults(results)
|
|
680
|
-
|
|
749
|
+
output: formatOrchestrationResults(results, goalResults)
|
|
750
|
+
+ (kind === 'failed' ? `\n실패/차단: ${reason} · 다음: ${next}` : `\n다음 조회: ${next}`)
|
|
751
|
+
+ '\n이 턴에서 파일을 직접 편집하지 마세요',
|
|
752
|
+
ok: kind !== 'failed',
|
|
753
|
+
kind,
|
|
754
|
+
next,
|
|
755
|
+
missing: [ ...(goalResults.length === 0 || goalResults.some(result => !result.runId) ? ['runId'] : []), 'node' ],
|
|
756
|
+
goalResults,
|
|
757
|
+
...(kind === 'failed' ? { reason } : {}),
|
|
681
758
|
...(fabricDecomposition ? { fabricDecomposition } : {}),
|
|
682
759
|
};
|
|
683
760
|
}
|
|
@@ -751,12 +828,12 @@ export const selfImplementRuntime: ToolRuntime<SelfImplementRuntimeRequest, Self
|
|
|
751
828
|
...(runId ? { runId } : {}),
|
|
752
829
|
}, { pipelineDeps });
|
|
753
830
|
if (!outcome.ok) {
|
|
754
|
-
return { output: withGoalAuthorFailure(
|
|
831
|
+
return { output: withGoalAuthorFailure(`하니스가 발사되지 않았거나 실패했습니다: ${outcome.message} · 다음: 실패 이유를 확인한 뒤 다시 요청하세요 · 이 턴에서 파일을 직접 편집하지 마세요`), ok: false, kind: 'failed', reason: outcome.message, next: '실패 이유를 확인한 뒤 다시 요청', missing: ['runId', 'node'] };
|
|
755
832
|
}
|
|
756
833
|
if (outcome.kind !== 'self') {
|
|
757
834
|
return {
|
|
758
|
-
output: withGoalAuthorFailure('
|
|
759
|
-
ok: true,
|
|
835
|
+
output: withGoalAuthorFailure('관측 전용이라 런을 발사하지 않았습니다 · 코드도 안 바꿨습니다 · 이 결과를 구현 실패로 읽고 직접 Write/Edit 하지 마세요'),
|
|
836
|
+
ok: true, kind: 'observed-only', observed: true, missing: ['runId', 'node'], next: '관측 전용 모드를 끈 뒤 다시 요청',
|
|
760
837
|
};
|
|
761
838
|
}
|
|
762
839
|
|
|
@@ -764,6 +841,11 @@ export const selfImplementRuntime: ToolRuntime<SelfImplementRuntimeRequest, Self
|
|
|
764
841
|
return {
|
|
765
842
|
output: withGoalAuthorFailure(formatResult(feature, result, documentReferences)),
|
|
766
843
|
ok: result.ok,
|
|
844
|
+
kind: result.ok ? (result.runId ? 'launched' : 'pending') : 'failed',
|
|
845
|
+
next: result.runId ? `eln self run ${result.runId}` : result.ok ? 'eln self running-runs' : '실패 이유를 확인한 뒤 다시 요청',
|
|
846
|
+
missing: [...(result.runId ? [] : ['runId']), ...(result.node ? [] : ['node'])],
|
|
847
|
+
...(!result.ok ? { reason: result.detail ?? result.stage } : {}),
|
|
848
|
+
runId: result.runId,
|
|
767
849
|
stage: result.stage,
|
|
768
850
|
node: result.node,
|
|
769
851
|
...(result.prUrl ? { prUrl: result.prUrl } : {}),
|
|
@@ -775,28 +857,51 @@ export const selfImplementRuntime: ToolRuntime<SelfImplementRuntimeRequest, Self
|
|
|
775
857
|
|
|
776
858
|
if (nonBlocking) {
|
|
777
859
|
const runId = allocatedRunId ?? mintRunId();
|
|
860
|
+
const requestId = runId;
|
|
861
|
+
const lookup = `eln logs --category self-implement --event runtime.non-blocking-completed,runtime.non-blocking-failed --grep ${requestId} --since 3h --all --include-test`;
|
|
778
862
|
void drive(runId).then(
|
|
779
863
|
(result) => {
|
|
780
|
-
debug.log('self-implement', 'runtime.non-blocking-completed', {
|
|
781
|
-
|
|
864
|
+
debug.log('self-implement', result.kind === 'failed' ? 'runtime.non-blocking-failed' : 'runtime.non-blocking-completed', {
|
|
865
|
+
requestId,
|
|
866
|
+
kind: result.kind,
|
|
867
|
+
...(result.runId ? { runId: result.runId } : {}),
|
|
868
|
+
...(result.reason ? { reason: result.reason } : {}),
|
|
869
|
+
next: result.next ?? '요청 결과를 확인한 뒤 다시 요청',
|
|
782
870
|
ok: result.ok,
|
|
783
871
|
...(result.stage === undefined ? {} : { stage: result.stage }),
|
|
784
|
-
});
|
|
872
|
+
}, result.kind === 'failed' ? { level: 'warn' } : undefined);
|
|
785
873
|
},
|
|
786
874
|
(error) => {
|
|
875
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
787
876
|
debug.log('self-implement', 'runtime.non-blocking-failed', {
|
|
788
|
-
|
|
789
|
-
|
|
877
|
+
requestId,
|
|
878
|
+
kind: 'failed',
|
|
879
|
+
reason,
|
|
880
|
+
next: '요청과 실패 이유를 확인한 뒤 다시 요청',
|
|
790
881
|
}, { level: 'warn' });
|
|
791
882
|
},
|
|
792
883
|
);
|
|
793
884
|
return {
|
|
794
885
|
ok: true,
|
|
795
|
-
|
|
886
|
+
kind: 'pending',
|
|
796
887
|
invocationMode: 'non-blocking',
|
|
797
|
-
|
|
888
|
+
requestId,
|
|
889
|
+
next: lookup,
|
|
890
|
+
missing: ['runId', 'node'],
|
|
891
|
+
output: `하니스 요청을 접수했습니다 · 런과 노드는 아직 미관측 · 요청 ID ${requestId}(발사 증거 아님) · 다음 조회: ${lookup} · 이 턴에서 파일을 직접 편집하지 마세요\n[self-implement] non-blocking pending`,
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
return await drive();
|
|
895
|
+
} catch (error) {
|
|
896
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
897
|
+
return {
|
|
898
|
+
ok: false,
|
|
899
|
+
kind: 'failed',
|
|
900
|
+
reason,
|
|
901
|
+
next: '요청과 실패 이유를 확인한 뒤 다시 요청',
|
|
902
|
+
missing: ['runId', 'node'],
|
|
903
|
+
output: `SelfImplement 실패: ${reason} · 다음: 요청과 실패 이유를 확인한 뒤 다시 요청 · 이 턴에서 파일을 직접 편집하지 마세요`,
|
|
798
904
|
};
|
|
799
905
|
}
|
|
800
|
-
return drive();
|
|
801
906
|
},
|
|
802
907
|
};
|
package/src/session/index.ts
CHANGED
|
@@ -142,10 +142,20 @@ function migrateLegacySessions(): void {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
function stateRoot(): string {
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
145
|
+
const universe = process.env.ELANOUS_STATE_DIR?.trim();
|
|
146
|
+
const root = universe
|
|
147
|
+
? join(universe, 'elanous')
|
|
148
|
+
: join(
|
|
149
|
+
process.env.XDG_STATE_HOME && process.env.XDG_STATE_HOME.trim()
|
|
150
|
+
? process.env.XDG_STATE_HOME
|
|
151
|
+
: join(homedir(), '.local', 'state'),
|
|
152
|
+
'elanous',
|
|
153
|
+
);
|
|
154
|
+
try {
|
|
155
|
+
const { debug } = require('../debug/log.js') as typeof import('../debug/log.js');
|
|
156
|
+
debug.log('session.active', 'pointer-root', { root, universe });
|
|
157
|
+
} catch { /* 관측 실패가 포인터 경로 결정을 막지 않는다 */ }
|
|
158
|
+
return root;
|
|
149
159
|
}
|
|
150
160
|
|
|
151
161
|
function indexPath(root: string = sessionRoot()): string { return join(root, 'index.json'); }
|
|
@@ -4,6 +4,7 @@ import { getUserConfig } from '../user-config.js';
|
|
|
4
4
|
import { resolve } from 'node:path';
|
|
5
5
|
import type { ModelFamily } from '../models/prompts.js';
|
|
6
6
|
import type { InputSourceKind, InputSourceRef } from '../input/input-source-kind.js';
|
|
7
|
+
import type { ControlTurnIntent, SubmitTurnIntent } from '../input/input-intent.js';
|
|
7
8
|
import { getSessionCwd } from '../session/working-dir.js';
|
|
8
9
|
import {
|
|
9
10
|
armQuickControlOnce,
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
import type { LLMMessage, LLMToolSpec } from '../llm.js';
|
|
23
24
|
import type { LLMToolDef } from '../plugins/core/types.js';
|
|
24
25
|
import type { ToolRunResult, ToolRuntime } from '../tool-runtime/types.js';
|
|
26
|
+
import { blockWriteToolSearchHydration, type ToolSearchRunResult } from '../tool-runtime/tool-search-runtime.js';
|
|
25
27
|
import { findNativeTool, isNativeToolModelExposed } from '../native-tool-catalog.js';
|
|
26
28
|
import { hasAstGrep } from '../skills/tools/ast-grep.js';
|
|
27
29
|
import { buildOmniSearchTool, omniSearchAvailable } from '../skills/tools/omni-search.js';
|
|
@@ -1731,6 +1733,8 @@ const ESSENTIAL_NATIVE_RULE_IDS = [
|
|
|
1731
1733
|
'agent', 'agent-output', 'agent-reply', 'agent-stop', 'agent-list', 'self-implement', 'self-orchestrate', 'dev-harness',
|
|
1732
1734
|
] as const;
|
|
1733
1735
|
const ESSENTIAL_HOST_FAMILY_IDS = ['self-ops', 'release-read'] as const;
|
|
1736
|
+
// Keep this TUI essential addition out of the shared control/agent self-ops family.
|
|
1737
|
+
const ESSENTIAL_HOST_EXTRA_NAMES = ['logs_query'] as const;
|
|
1734
1738
|
const ESSENTIAL_RUNTIME_FAMILY_IDS = ['bash', 'run-shell', 'pty-shell', 'run-tests', 'elanous_skills_list', 'skill_exec', 'code-grounding'] as const;
|
|
1735
1739
|
const ESSENTIAL_OPTIONAL_TOOL_NAMES = [
|
|
1736
1740
|
'GetDashboardState',
|
|
@@ -1764,7 +1768,11 @@ function buildEssentialSessionRuntimeToolSpecs(opts: {
|
|
|
1764
1768
|
.filter((tool): tool is LLMToolDef => !!tool)
|
|
1765
1769
|
.map(toSpec)
|
|
1766
1770
|
: [];
|
|
1767
|
-
})
|
|
1771
|
+
}).concat(ESSENTIAL_HOST_EXTRA_NAMES
|
|
1772
|
+
.filter(name => isToolAvailable(name, opts.toolAvailability))
|
|
1773
|
+
.map(name => hostByName.get(name))
|
|
1774
|
+
.filter((tool): tool is LLMToolDef => !!tool)
|
|
1775
|
+
.map(toSpec));
|
|
1768
1776
|
const runtimeById = new Map(opts.runtimeTools.map(tool => [tool.id, tool] as const));
|
|
1769
1777
|
const runtimeSpecs = ESSENTIAL_RUNTIME_FAMILY_IDS.flatMap(id => {
|
|
1770
1778
|
const rule = RUNTIME_TOOL_FAMILY_RULE_MAP.get(id);
|
|
@@ -1833,12 +1841,23 @@ export function buildSessionRuntimeToolSpecs(opts: {
|
|
|
1833
1841
|
// toolset (incident 2026-05-04 — Opus replied "no file tools in this
|
|
1834
1842
|
// session" while Bash was actually exposed at position 7+).
|
|
1835
1843
|
const finalSpecs: LLMToolSpec[] = opts.rich === false
|
|
1836
|
-
?
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1844
|
+
? (() => {
|
|
1845
|
+
const essentialSpecs = buildEssentialSessionRuntimeToolSpecs({
|
|
1846
|
+
hostTools: opts.hostTools,
|
|
1847
|
+
runtimeTools: opts.runtimeTools ?? [],
|
|
1848
|
+
optionalTools: opts.optionalTools ?? [],
|
|
1849
|
+
toolAvailability: opts.toolAvailability,
|
|
1850
|
+
});
|
|
1851
|
+
if (!hasDevHarnessIntent(opts.userText)) return essentialSpecs;
|
|
1852
|
+
const droppedNames = new Set(['Edit', 'Write']);
|
|
1853
|
+
const filteredSpecs = essentialSpecs.filter(spec => !droppedNames.has(spec.name));
|
|
1854
|
+
if (filteredSpecs.length !== essentialSpecs.length) {
|
|
1855
|
+
debug.log('session-runtime.tools', 'harness-phrase-write-tools-dropped', {
|
|
1856
|
+
dropped: ['Edit', 'Write'],
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
return filteredSpecs;
|
|
1860
|
+
})()
|
|
1842
1861
|
: [
|
|
1843
1862
|
...dynamicNativeTools,
|
|
1844
1863
|
...hostSpecs,
|
|
@@ -2084,6 +2103,10 @@ export async function dispatchSessionRuntimeTool(
|
|
|
2084
2103
|
args: Record<string, unknown>,
|
|
2085
2104
|
deps: SessionRuntimeDispatchDeps,
|
|
2086
2105
|
): Promise<unknown> {
|
|
2106
|
+
const finishRuntimeResult = (result: unknown): unknown =>
|
|
2107
|
+
name === 'ToolSearch' && isDevHarnessIntent(deps.userText ?? '')
|
|
2108
|
+
? blockWriteToolSearchHydration(result as ToolSearchRunResult)
|
|
2109
|
+
: result;
|
|
2087
2110
|
// Fix W-bis (2026-04-25): codex family always enables scoped-analysis
|
|
2088
2111
|
// mode regardless of userText vocabulary. Rationale (see docs/RESEARCH-
|
|
2089
2112
|
// codex-reread-pathology-2refs-2026-04-25.md): codex's search-heavy
|
|
@@ -2178,8 +2201,9 @@ export async function dispatchSessionRuntimeTool(
|
|
|
2178
2201
|
const nativeRuntime = nativeRule?.preferRuntime ? deps.getToolRuntime(name) : undefined;
|
|
2179
2202
|
if (nativeRuntime) {
|
|
2180
2203
|
const result = await deps.dispatchToolRuntime(name, normalizedArgs, deps.toolCallId);
|
|
2181
|
-
|
|
2182
|
-
|
|
2204
|
+
const safeResult = finishRuntimeResult(result);
|
|
2205
|
+
rememberCandidateListingResult(name, normalizedArgs, safeResult, scopedAnalysis, deps.searchPlannerState);
|
|
2206
|
+
return safeResult;
|
|
2183
2207
|
}
|
|
2184
2208
|
if (nativeRule?.dispatch) {
|
|
2185
2209
|
let result: unknown;
|
|
@@ -2213,8 +2237,9 @@ export async function dispatchSessionRuntimeTool(
|
|
|
2213
2237
|
const isPtyTool = name.startsWith('PtyShell') || name.startsWith('pty_shell');
|
|
2214
2238
|
if (!isPtyTool || deps.ptyDashboardOn) {
|
|
2215
2239
|
const result = await deps.dispatchToolRuntime(name, normalizedArgs, deps.toolCallId);
|
|
2216
|
-
|
|
2217
|
-
|
|
2240
|
+
const safeResult = finishRuntimeResult(result);
|
|
2241
|
+
rememberCandidateListingResult(name, normalizedArgs, safeResult, scopedAnalysis, deps.searchPlannerState);
|
|
2242
|
+
return safeResult;
|
|
2218
2243
|
}
|
|
2219
2244
|
}
|
|
2220
2245
|
|
|
@@ -2699,6 +2724,25 @@ function hasDevHarnessIntent(text: string): boolean {
|
|
|
2699
2724
|
return hasAny(text, DEV_HARNESS_INTENT_TERMS);
|
|
2700
2725
|
}
|
|
2701
2726
|
|
|
2727
|
+
export function isDevHarnessIntent(text: string): boolean {
|
|
2728
|
+
return hasDevHarnessIntent(text);
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
/** The dashboard calls this immediately after submitIntent resolution. A
|
|
2732
|
+
* fixed plain turn becomes the same control turn used by `/harness ask`, so
|
|
2733
|
+
* the model dispatch branch is never reached for that turn. */
|
|
2734
|
+
export function routeDevHarnessSubmitIntent(
|
|
2735
|
+
intent: SubmitTurnIntent | ControlTurnIntent,
|
|
2736
|
+
): SubmitTurnIntent | ControlTurnIntent {
|
|
2737
|
+
if (intent.kind !== 'submit-turn' || intent.route !== 'plain' || !isDevHarnessIntent(intent.text)) return intent;
|
|
2738
|
+
return {
|
|
2739
|
+
kind: 'control-turn',
|
|
2740
|
+
source: intent.source,
|
|
2741
|
+
command: 'slash-command',
|
|
2742
|
+
commandText: `/harness ask ${intent.text}`,
|
|
2743
|
+
};
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2702
2746
|
export function shouldExposeDevHarnessSessionTool(text: string): boolean {
|
|
2703
2747
|
return isDevHarnessModelSurfaceEnabled() && hasDevHarnessIntent(text);
|
|
2704
2748
|
}
|
package/src/task-agent/cover.ts
CHANGED
|
@@ -32,7 +32,10 @@ export interface TaskAgentCoverInput {
|
|
|
32
32
|
/** null means the pending-landing age source could not be read; [] means no pending landings. */
|
|
33
33
|
pendingLandingAgeSeconds?: readonly number[] | null;
|
|
34
34
|
/** A source reached the query safety limit; its aggregate is not known to be complete. */
|
|
35
|
-
truncated?: { taskAgentActions: boolean; allLands: boolean };
|
|
35
|
+
truncated?: { taskAgentActions: boolean; allLands: boolean; goalAsks?: boolean; goalRuns?: boolean };
|
|
36
|
+
/** Completed authoring provenance and terminal goal records. undefined = not wired; null = unreadable. */
|
|
37
|
+
goalAsks?: readonly { authorRunId: string; goalId: string }[] | null;
|
|
38
|
+
goalRuns?: readonly { goalId: string; outcome: string; stage?: string }[] | null;
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
export type LandingFailureReason = 'load' | 'oom' | 'time-limit' | 'conflict' | 'review' | 'other';
|
|
@@ -161,22 +164,22 @@ export function formatTaskAgentCover(row: CoverRow): string {
|
|
|
161
164
|
/** 7동사의 이름 — 리뷰·착지·수리·검증·저작·구현·정리 (decision·deliver 는 7동사 밖이라 세지 않는다). */
|
|
162
165
|
export type SevenVerbName = '리뷰' | '착지' | '수리' | '검증' | '저작' | '구현' | '정리';
|
|
163
166
|
|
|
164
|
-
/** no-source 는 출처 배선이
|
|
167
|
+
/** no-source 는 출처 배선이 없는 동사 · unreadable 은 해당 동사의 원천을 완전히 못 읽은 것이다. */
|
|
165
168
|
export type SevenVerbState = 'live' | 'shadow-only' | 'none' | 'no-source' | 'unreadable';
|
|
166
169
|
|
|
167
170
|
/** 7동사 출처 표의 한 칸 — source 는 그 동사를 관측하는 TASK-AGENT action kind, null 이면 출처가 아직 없다. */
|
|
168
171
|
export interface SevenVerbSource {
|
|
169
172
|
verb: SevenVerbName;
|
|
170
|
-
source: Exclude<NextActionKind, 'decision' | 'deliver'> | null;
|
|
173
|
+
source: Exclude<NextActionKind, 'decision' | 'deliver'> | 'goal-ask+goal-run' | null;
|
|
171
174
|
}
|
|
172
175
|
|
|
173
|
-
/** TA-THEME-VERBS-LIVE —
|
|
176
|
+
/** TA-THEME-VERBS-LIVE — 저작은 완료 골 조인; 구현·정리는 아직 출처가 없다. */
|
|
174
177
|
export const SEVEN_VERB_SOURCES: readonly SevenVerbSource[] = [
|
|
175
178
|
{ verb: '리뷰', source: 'review' },
|
|
176
179
|
{ verb: '착지', source: 'land' },
|
|
177
180
|
{ verb: '수리', source: 'retry' },
|
|
178
181
|
{ verb: '검증', source: 'green' },
|
|
179
|
-
{ verb: '저작', source:
|
|
182
|
+
{ verb: '저작', source: 'goal-ask+goal-run' },
|
|
180
183
|
{ verb: '구현', source: null },
|
|
181
184
|
{ verb: '정리', source: null },
|
|
182
185
|
];
|
|
@@ -197,12 +200,21 @@ export interface SevenVerbLive {
|
|
|
197
200
|
measuredVerbs: number;
|
|
198
201
|
}
|
|
199
202
|
|
|
200
|
-
/**
|
|
203
|
+
/** 행동 동사는 창 안의 done, 저작은 완료한 저작 원장과 착지 골의 조인. 각 출처의 null·절단은 해당 동사만 unreadable. */
|
|
201
204
|
export function measureSevenVerbLive(input: TaskAgentCoverInput): SevenVerbLive {
|
|
202
205
|
const unreadable = input.taskAgentActions === null || input.truncated?.taskAgentActions === true;
|
|
203
206
|
const verbs = SEVEN_VERB_SOURCES.map(({ verb, source }): SevenVerbLiveRow => {
|
|
204
|
-
if (unreadable) return { verb, source, liveDone: null, shadow: null, state: 'unreadable' };
|
|
205
207
|
if (source === null) return { verb, source: null, liveDone: null, shadow: null, state: 'no-source' };
|
|
208
|
+
if (source !== 'goal-ask+goal-run' && unreadable) return { verb, source, liveDone: null, shadow: null, state: 'unreadable' };
|
|
209
|
+
if (source === 'goal-ask+goal-run') {
|
|
210
|
+
if (input.goalAsks == null || input.goalRuns == null || input.truncated?.goalAsks || input.truncated?.goalRuns) {
|
|
211
|
+
return { verb, source, liveDone: null, shadow: null, state: 'unreadable' };
|
|
212
|
+
}
|
|
213
|
+
const landed = new Set(input.goalRuns.filter((run) =>
|
|
214
|
+
run.stage === 'merged' && (run.outcome === 'completed' || run.outcome === 'merged')).map((run) => run.goalId));
|
|
215
|
+
const liveDone = new Set(input.goalAsks.filter((ask) => ask.authorRunId && landed.has(ask.goalId)).map((ask) => ask.goalId)).size;
|
|
216
|
+
return { verb, source, liveDone, shadow: 0, state: liveDone > 0 ? 'live' : 'none' };
|
|
217
|
+
}
|
|
206
218
|
const actions = input.taskAgentActions!.filter((action) => action.kind === source);
|
|
207
219
|
const liveDone = actions.filter((action) => action.result === 'done').length;
|
|
208
220
|
const shadow = actions.filter((action) => action.result === 'shadow').length;
|
|
@@ -210,7 +222,7 @@ export function measureSevenVerbLive(input: TaskAgentCoverInput): SevenVerbLive
|
|
|
210
222
|
});
|
|
211
223
|
return {
|
|
212
224
|
verbs,
|
|
213
|
-
liveVerbs: unreadable ? null : verbs.filter((row) => row.state === 'live').length,
|
|
225
|
+
liveVerbs: verbs.some((row) => row.state === 'unreadable' && row.source !== null) ? null : verbs.filter((row) => row.state === 'live').length,
|
|
214
226
|
measuredVerbs: SEVEN_VERB_SOURCES.filter((row) => row.source !== null).length,
|
|
215
227
|
};
|
|
216
228
|
}
|
|
@@ -218,7 +230,14 @@ export function measureSevenVerbLive(input: TaskAgentCoverInput): SevenVerbLive
|
|
|
218
230
|
/** 한 줄 요약 — 못 읽었으면 n 자리를 `-` 로 적는다(0 으로 읽히지 않게). */
|
|
219
231
|
export function formatSevenVerbLive(measurement: SevenVerbLive): string {
|
|
220
232
|
const total = SEVEN_VERB_SOURCES.length;
|
|
221
|
-
if (measurement.liveVerbs === null)
|
|
233
|
+
if (measurement.liveVerbs === null) {
|
|
234
|
+
const actionUnreadable = measurement.verbs.some((row) => row.source === 'review' && row.state === 'unreadable');
|
|
235
|
+
const authorUnreadable = measurement.verbs.some((row) => row.source === 'goal-ask+goal-run' && row.state === 'unreadable');
|
|
236
|
+
const reason = actionUnreadable && authorUnreadable ? '행동·저작·골 원장 null·절단'
|
|
237
|
+
: actionUnreadable ? '행동 원장 null·절단' : '저작·골 원장 null·절단';
|
|
238
|
+
const noSource = measurement.verbs.filter((row) => row.state === 'no-source').map((row) => row.verb).join('·');
|
|
239
|
+
return `${total}동사 live -/${total} · 못 잼(${reason})${noSource ? ` · 출처 없음 ${noSource}` : ''}`;
|
|
240
|
+
}
|
|
222
241
|
const noSource = measurement.verbs.filter((row) => row.state === 'no-source').map((row) => row.verb).join('·');
|
|
223
242
|
return `${total}동사 live ${measurement.liveVerbs}/${total}${noSource ? ` · 출처 없음 ${noSource}` : ''}`;
|
|
224
243
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* - `review`: 수확 가능 런의 PR 에 elanous 리뷰를 «한 번» 요청한다 — PR 머리(sha)마다 한 번(카드 `reviewRequests` 이력).
|
|
5
5
|
* 요청만 한다: 결과로 착지·재발사로 잇지 않는다(실행부 `executeNextAction` 의 review→land 사슬을 타지 않는다).
|
|
6
|
-
* TA-REVIEW-RESULT-CAPTURE — 자식 stdout(`--json`)은 결과 파일로 받고,
|
|
6
|
+
* TA-REVIEW-RESULT-CAPTURE — 자식 stdout(`--json`)은 결과 파일로 받고, 자식 완료 뒤 해당 카드만 회수·재판정한다.
|
|
7
7
|
* 회수한 pass 는 그 틱의 판단부·land 관문 입력(`selfReview`)이 될 뿐 land 판정 로직은 그대로다.
|
|
8
8
|
* - `propose-green`: 카드에 green «제안»(`greenProposal`)만 적는다 — 체크리스트는 손대지 않는다.
|
|
9
9
|
* - `propose-land`: 카드·현재 머리의 self review 통과(pass «또는» warn ⊕ must-fix 0 · TA-LAND-WARN-MUSTFIX0)·non-draft 를 확인하고 기존 `pr land` 를 머리 고정으로 한 번 부른다.
|
|
@@ -25,7 +25,7 @@ import type { DetachedSpawn } from '../cli/tasks-cli.js';
|
|
|
25
25
|
import { LIVE_MOVE_HISTORY_EVENT, taskAgentStatePath, updateTaskCard, type TaskCard } from './task-hand.js';
|
|
26
26
|
import { LIVE_RETRY_MOVES } from './judge.js';
|
|
27
27
|
import { decisionCardGrounds, decisionRaiseArgs, type DecisionCardEvidence, type NextAction } from './actions.js';
|
|
28
|
-
import { reviewResultPath } from './review-result-capture.js';
|
|
28
|
+
import { reviewResultPath, TA_REVIEW_COMPLETION_CARD_ENV, TA_REVIEW_COMPLETION_RESULT_ENV, TA_REVIEW_COMPLETION_STATE_ENV } from './review-result-capture.js';
|
|
29
29
|
import { buildPriorFixContext, PRIOR_FIX_SCOPE_LINE, priorFixBlock, priorFixObservation, type PriorDiffReader, type PriorFixContext } from './review-prior-fix.js';
|
|
30
30
|
import { createLandWorktree, localHead, observeLandWorktree, type LandWorktree, type LandWorktreeFailure, type LandWorktreeInput, type LandWorktreeRemoval } from './land-worktree.js';
|
|
31
31
|
|
|
@@ -208,13 +208,18 @@ export function liveReviewArgs(entry: string, configDir: string, pr: number, int
|
|
|
208
208
|
* 파이프가 아니라 파일 — 부모가 먼저 끝나도 자식이 EPIPE 로 죽지 않는다). 없으면 종전과 같다(stdio ignore).
|
|
209
209
|
* `opts` 는 시험 몫(spawn·진입점·우주) — 기본은 실물.
|
|
210
210
|
*/
|
|
211
|
-
export async function defaultRequestReview(pr: number, intent: string, cwd?: string, opts: { spawn?: DetachedSpawn; entry?: string; configDir?: string; resultPath?: string } = {}): Promise<void> {
|
|
211
|
+
export async function defaultRequestReview(pr: number, intent: string, cwd?: string, opts: { spawn?: DetachedSpawn; entry?: string; configDir?: string; resultPath?: string; cardId?: string; statePath?: string } = {}): Promise<void> {
|
|
212
212
|
const { spawnDetachedConfirmed, ELANOUS_CLI_ENTRY } = await import('../cli/tasks-cli.js');
|
|
213
213
|
const { getElanousConfigDirOverride } = await import('../elanous-config-dir.js');
|
|
214
214
|
const { effectiveInstanceRoot } = await import('../instance/resolve.js');
|
|
215
215
|
const configDir = opts.configDir ?? getElanousConfigDirOverride() ?? effectiveInstanceRoot();
|
|
216
216
|
const base = opts.spawn ?? (spawn as unknown as DetachedSpawn);
|
|
217
217
|
const args = liveReviewArgs(opts.entry ?? ELANOUS_CLI_ENTRY, configDir, pr, intent);
|
|
218
|
+
const completionEnv = opts.resultPath && opts.cardId && opts.statePath ? {
|
|
219
|
+
[TA_REVIEW_COMPLETION_CARD_ENV]: opts.cardId,
|
|
220
|
+
[TA_REVIEW_COMPLETION_STATE_ENV]: opts.statePath,
|
|
221
|
+
[TA_REVIEW_COMPLETION_RESULT_ENV]: opts.resultPath,
|
|
222
|
+
} : undefined;
|
|
218
223
|
if (!opts.resultPath) { await spawnDetachedConfirmed(base, process.execPath, args, undefined, cwd); return; }
|
|
219
224
|
let fd: number;
|
|
220
225
|
try {
|
|
@@ -229,7 +234,7 @@ export async function defaultRequestReview(pr: number, intent: string, cwd?: str
|
|
|
229
234
|
const toFile: DetachedSpawn = (command, spawnArgs, options) =>
|
|
230
235
|
(base as unknown as (c: string, a: string[], o: unknown) => ReturnType<DetachedSpawn>)(command, spawnArgs, { ...options, stdio: ['ignore', fd, 'ignore'] });
|
|
231
236
|
try {
|
|
232
|
-
await spawnDetachedConfirmed(toFile, process.execPath, args, undefined, cwd);
|
|
237
|
+
await spawnDetachedConfirmed(toFile, process.execPath, args, undefined, cwd, completionEnv);
|
|
233
238
|
} finally {
|
|
234
239
|
try { closeSync(fd); } catch { /* the child keeps its own copy */ }
|
|
235
240
|
}
|
|
@@ -360,7 +365,7 @@ export async function executeLiveReview(
|
|
|
360
365
|
try { (deps.log ?? ((c, e, d) => debug.log(c, e, d)))('task-agent', 'review-prior-fix-filled', priorFixObservation(card.id, pr, prior)); } catch { /* fail-soft */ }
|
|
361
366
|
}
|
|
362
367
|
const intent = liveReviewIntent(card, ctx.runId, ctx.stopReason, prior);
|
|
363
|
-
await (deps.requestReview ? deps.requestReview(pr, intent, cwd, { resultPath }) : defaultRequestReview(pr, intent, cwd, { resultPath }));
|
|
368
|
+
await (deps.requestReview ? deps.requestReview(pr, intent, cwd, { resultPath }) : defaultRequestReview(pr, intent, cwd, { resultPath, cardId: card.id, statePath: path }));
|
|
364
369
|
} catch (error) {
|
|
365
370
|
const reason = error instanceof Error ? error.message : String(error);
|
|
366
371
|
updateTaskCard(path, card.id, (current) => current?.reviewRequests?.some((entry) => entry.pr === pr && entry.head === head!.head && entry.at === at)
|
|
@@ -560,11 +565,14 @@ export async function executeLiveLand(
|
|
|
560
565
|
const path = deps.statePath ?? taskAgentStatePath();
|
|
561
566
|
const at = (deps.now ?? (() => new Date()))().toISOString();
|
|
562
567
|
const local = (deps.landWorktree?.localHead ?? localHead)(cwd);
|
|
568
|
+
const landKey = `land:${card.id}:${view.head.slice(0, 12)}`;
|
|
563
569
|
const needsLandWorktree = !!local && local.toLowerCase() !== view.head.toLowerCase();
|
|
564
570
|
const createCap = landWorktreeCreateCap();
|
|
565
571
|
let claimed = false;
|
|
566
572
|
let missing = false;
|
|
567
573
|
let cappedReason: string | undefined;
|
|
574
|
+
// This state-file lock is the cross-process card/head land claim. Both completion workers
|
|
575
|
+
// must pass it; an unfinished or successful attempt makes the second worker skip.
|
|
568
576
|
// TA-LAND-RETRY-ON-INFRA — 같은 머리의 앞 시도들(잠금 안에서 읽은 «지금» 카드) · 재시도면 그 근거.
|
|
569
577
|
let prior: LandAttempt[] = [];
|
|
570
578
|
try {
|
|
@@ -590,7 +598,7 @@ export async function executeLiveLand(
|
|
|
590
598
|
if (!claimed) {
|
|
591
599
|
const last = prior.at(-1);
|
|
592
600
|
const why = last ? ` (attempts ${prior.length} · last ${last.ok === false ? `class ${last.class ?? 'unclassified'}${last.reason ? ` ${last.reason}` : ''}` : last.ok === true ? 'ok' : 'unfinished'})` : '';
|
|
593
|
-
return observe(deps, withRepo({ kind, card: card.id, ok: true, executed: false, detail: `already attempted land for #${pr} head ${view.head.slice(0, 12)}${why}` }));
|
|
601
|
+
return observe(deps, withRepo({ kind, card: card.id, ok: true, executed: false, detail: `already attempted land for #${pr} head ${view.head.slice(0, 12)} · ${landKey} claimed — skip${why}` }));
|
|
594
602
|
}
|
|
595
603
|
const attempt = prior.length + 1;
|
|
596
604
|
if (attempt > 1) {
|