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
package/src/dashboard/index.ts
CHANGED
|
@@ -965,6 +965,7 @@ import {
|
|
|
965
965
|
enterSessionControlMode,
|
|
966
966
|
exitSessionControlMode,
|
|
967
967
|
isSessionControlActive,
|
|
968
|
+
routeDevHarnessSubmitIntent,
|
|
968
969
|
parseSessionControlSlash,
|
|
969
970
|
parseSessionSurfaceSlash,
|
|
970
971
|
resolveSessionInputModeFromChatMode,
|
|
@@ -1023,7 +1024,7 @@ import { expandPromptReferences } from '../prompt/references.js';
|
|
|
1023
1024
|
import { spawnCodingAgent, CodingAgentBinaryMissing } from '../terminal/coding-agent.js';
|
|
1024
1025
|
import type { TerminalSession } from '../terminal/session-registry.js';
|
|
1025
1026
|
import { createSessionPickerModal } from '../session/picker-modal.js';
|
|
1026
|
-
import { listSessions as listChatSessionsForResume } from '../session/index.js';
|
|
1027
|
+
import { listSessions as listChatSessionsForResume, adoptSession as adoptChatSession, setActiveSessionId as markActiveChatSession } from '../session/index.js';
|
|
1027
1028
|
// TUI 부활 S-b — 타임트래블 포크 엔진 (codex ForkSnapshot 동형 · 2026-07-09
|
|
1028
1029
|
// 이식 후 호출처 0 이던 방치 자산 회수). 순수 히스토리 절단 로직만 소비;
|
|
1029
1030
|
// 영속은 세션 스토어(forkSessionFromHistory)가 담당한다.
|
|
@@ -1179,6 +1180,20 @@ import { buildDebugCallStack } from '../debug/call-stack.js';
|
|
|
1179
1180
|
import { buildAstGrepHostTool } from '../skills/tools/ast-grep.js';
|
|
1180
1181
|
import { buildLspHostTool } from '../skills/tools/lsp/index.js';
|
|
1181
1182
|
import { PluginHost } from '../plugins/core/host.js';
|
|
1183
|
+
|
|
1184
|
+
/** Register the same L2 host tools used by the live TUI before each turn's catalog is assembled. */
|
|
1185
|
+
export function registerDashboardCoreHostTools(host: Pick<PluginHost, 'registerHostTool'>): void {
|
|
1186
|
+
const coreMod = require('../domains/core-tools.js') as typeof import('../domains/core-tools.js');
|
|
1187
|
+
const core = coreMod.buildCoreTools();
|
|
1188
|
+
for (const spec of core.specs) {
|
|
1189
|
+
host.registerHostTool({
|
|
1190
|
+
name: spec.name,
|
|
1191
|
+
description: spec.description,
|
|
1192
|
+
parameters: spec.parameters,
|
|
1193
|
+
handler: async (args) => core.dispatch(spec.name, args as Record<string, unknown>),
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1182
1197
|
import { registerDashboardPaneHostTools } from './panes/host-tools.js';
|
|
1183
1198
|
import { DEFAULT_THEME_TOKENS, mergeThemeTokens, resolveThemeTokens, type ThemeTokenInput, type ThemeTokens } from '../theme/tokens.js';
|
|
1184
1199
|
import { resolveActiveTheme, setActivePresetInConfig } from './render/theme-resolver.js';
|
|
@@ -1407,6 +1422,8 @@ const isPluginBusy = (host: { active(): { plugin: { isBusy?: (s: any) => boolean
|
|
|
1407
1422
|
// ── Main dashboard ──
|
|
1408
1423
|
|
|
1409
1424
|
export interface ShowDashboardOptions {
|
|
1425
|
+
/** Internal startup timing signal for the first successfully flushed dashboard frame. */
|
|
1426
|
+
onFirstFrameRendered?: () => void;
|
|
1410
1427
|
/** Force debug sinks on at launch (file + mirror). Equivalent to
|
|
1411
1428
|
* typing `/debug on` immediately after startup. Paired with
|
|
1412
1429
|
* chatOnly=true by the `elanous --debug` CLI flag so every traced
|
|
@@ -1501,6 +1518,16 @@ export function buildDashboardTurnTypeaheadPromptZone(input: TurnTypeaheadPrompt
|
|
|
1501
1518
|
};
|
|
1502
1519
|
}
|
|
1503
1520
|
|
|
1521
|
+
/** The chat-main submit branch calls this before any model dispatch. Keeping
|
|
1522
|
+
* the decision here lets the dashboard test execute the same entry point. */
|
|
1523
|
+
export function resolveDashboardHarnessSubmitIntent(
|
|
1524
|
+
text: string,
|
|
1525
|
+
options: Parameters<typeof resolveDashboardChatMainSubmitIntent>[1],
|
|
1526
|
+
resolveIntent: typeof resolveDashboardChatMainSubmitIntent = resolveDashboardChatMainSubmitIntent,
|
|
1527
|
+
) {
|
|
1528
|
+
return routeDevHarnessSubmitIntent(resolveIntent(text, options));
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1504
1531
|
/** 프롬프트 zone 에 관측만 덧입힌다 — 렌더 산출은 `inner` 가 소유한다.
|
|
1505
1532
|
*
|
|
1506
1533
|
* ⭐ **왜 뽑았나**(무인 리뷰 must-fix · 2026-08-01): 종전엔 이 래퍼가 `zones.push({...})` 안에
|
|
@@ -1567,6 +1594,7 @@ export interface DashboardFrameInput {
|
|
|
1567
1594
|
promptCaret: CursorState | null;
|
|
1568
1595
|
suppressPromptArea: boolean;
|
|
1569
1596
|
onEssentialFrameCursorDecision?: (observation: EssentialFrameCursorObservation) => void;
|
|
1597
|
+
onFrameWriteComplete?: (error?: Error | null) => void;
|
|
1570
1598
|
}
|
|
1571
1599
|
|
|
1572
1600
|
export interface EssentialFrameCursorObservation {
|
|
@@ -1612,6 +1640,7 @@ export function renderDashboardFrame(
|
|
|
1612
1640
|
overlay: input.overlay,
|
|
1613
1641
|
force: input.force,
|
|
1614
1642
|
cursor: paintCursor(essentialDecision.cursor ?? input.coordinatorCursor ?? null),
|
|
1643
|
+
onWriteComplete: input.onFrameWriteComplete,
|
|
1615
1644
|
});
|
|
1616
1645
|
input.onEssentialFrameCursorDecision?.({
|
|
1617
1646
|
caret: essentialDecision.cursor,
|
|
@@ -1901,6 +1930,7 @@ export function renderDashboardFirstScreenBand(input: {
|
|
|
1901
1930
|
}
|
|
1902
1931
|
|
|
1903
1932
|
export async function showDashboard(opts: ShowDashboardOptions = {}): Promise<DashboardAction> {
|
|
1933
|
+
let firstDashboardFrameRendered = false;
|
|
1904
1934
|
await awaitGlobalPersonaLoad().catch(() => {});
|
|
1905
1935
|
const dashboardQuitConfirmationState = createQuitConfirmationState();
|
|
1906
1936
|
const signalQuitConfirmationState = createQuitConfirmationState();
|
|
@@ -1910,7 +1940,11 @@ export async function showDashboard(opts: ShowDashboardOptions = {}): Promise<Da
|
|
|
1910
1940
|
closeTui();
|
|
1911
1941
|
};
|
|
1912
1942
|
const exitDashboardTui = (): void => {
|
|
1913
|
-
closeDashboardForExit({
|
|
1943
|
+
closeDashboardForExit({
|
|
1944
|
+
closeTui: closeDashboardTui,
|
|
1945
|
+
confirmationState: dashboardQuitConfirmationState,
|
|
1946
|
+
getSessionId: (): string | undefined => acpSessionIdRef.current ?? undefined,
|
|
1947
|
+
});
|
|
1914
1948
|
};
|
|
1915
1949
|
// ⭐⭐⭐ `N1` — 신호 경로(`SIGINT`/`SIGTERM`)와 저수준 `Ctrl+C` 도 같은 종료를 타게 «등록»한다.
|
|
1916
1950
|
// ⛔ 종전엔 그 둘만 세션 안내를 «못 받았다»(일곱 경로 중 다섯만 받았다).
|
|
@@ -1920,6 +1954,7 @@ export async function showDashboard(opts: ShowDashboardOptions = {}): Promise<Da
|
|
|
1920
1954
|
closeTui: () => { /* 신호 경로: cleanup() 이 이미 복원했다 */ },
|
|
1921
1955
|
confirmationState: signalQuitConfirmationState,
|
|
1922
1956
|
forceQuit: true,
|
|
1957
|
+
getSessionId: (): string | undefined => acpSessionIdRef.current ?? undefined,
|
|
1923
1958
|
});
|
|
1924
1959
|
});
|
|
1925
1960
|
// E3 (§7.4 TS baseline reduction · 2026-05-17) — forward declarations
|
|
@@ -3342,6 +3377,8 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
3342
3377
|
// ⛔ `attachedSessionId`(대화 세션)와 «다른 축»이다 — 루프는 ACP 세션으로 스코프를 연다.
|
|
3343
3378
|
// ⚠️ 세션은 이 함수 «뒤»에서 만들어지므로 참조로 둔다(호출 시점엔 채워져 있다).
|
|
3344
3379
|
const acpSessionIdRef: { current: string | null } = { current: null };
|
|
3380
|
+
const adoptedAcpSessionIds = new Set<string>();
|
|
3381
|
+
let activeAcpSessionId: string | null = null;
|
|
3345
3382
|
const activeSessionIdForTypeahead = (): string | null => acpSessionIdRef.current;
|
|
3346
3383
|
const paintTurnTypeaheadEcho = (): void => {
|
|
3347
3384
|
const { rows: tr, cols: tc } = termSize();
|
|
@@ -10014,16 +10051,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
10014
10051
|
// family(전 surface 기본) — 이로써 TUI 에서도 "P2 왜 실패?" 를 엘라누스가 툴로 상황판단한다.
|
|
10015
10052
|
// fail-soft: 코어 도구 로드 실패가 대시보드 부팅을 막지 않는다.
|
|
10016
10053
|
try {
|
|
10017
|
-
|
|
10018
|
-
const core = coreMod.buildCoreTools();
|
|
10019
|
-
for (const spec of core.specs) {
|
|
10020
|
-
pluginHost.registerHostTool({
|
|
10021
|
-
name: spec.name,
|
|
10022
|
-
description: spec.description,
|
|
10023
|
-
parameters: spec.parameters,
|
|
10024
|
-
handler: async (args) => core.dispatch(spec.name, args as Record<string, unknown>),
|
|
10025
|
-
});
|
|
10026
|
-
}
|
|
10054
|
+
registerDashboardCoreHostTools(pluginHost);
|
|
10027
10055
|
} catch { /* fail-soft */ }
|
|
10028
10056
|
// LSP (Phase L3 of 내부 문서 `ROADMAP-lsp-integration`) — structural /
|
|
10029
10057
|
// symbol-level source queries via typescript-language-server. Pool
|
|
@@ -11530,6 +11558,11 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
11530
11558
|
// ⭐ Single flush: resolve the prompt caret after overlays.
|
|
11531
11559
|
const frameCursorDecision = display.cursorDecision();
|
|
11532
11560
|
renderDashboardFrame(baseLines, composeDashboardFrameWithCoordinatorModal(display, {
|
|
11561
|
+
onFrameWriteComplete: firstDashboardFrameRendered ? undefined : (error) => {
|
|
11562
|
+
if (error || firstDashboardFrameRendered) return;
|
|
11563
|
+
firstDashboardFrameRendered = true;
|
|
11564
|
+
try { opts.onFirstFrameRendered?.(); } catch { /* timing must not break dashboard */ }
|
|
11565
|
+
},
|
|
11533
11566
|
overlay: modalFrame,
|
|
11534
11567
|
force: forceFromCoordinator || modalKey !== lastModalKey,
|
|
11535
11568
|
cursorOwner: frameCursorDecision.owner,
|
|
@@ -17422,7 +17455,7 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
17422
17455
|
// resource_link blocks so dropped screenshots and PDFs flow
|
|
17423
17456
|
// naturally to the agent. debug.log gates the snapshot so
|
|
17424
17457
|
// off-state stays zero-cost.
|
|
17425
|
-
const submitIntent =
|
|
17458
|
+
const submitIntent = resolveDashboardHarnessSubmitIntent(input.text, {
|
|
17426
17459
|
stickyBackend: dashboardAcpChat.getSticky(),
|
|
17427
17460
|
source: input.externalSubmitSource,
|
|
17428
17461
|
});
|
|
@@ -17443,7 +17476,10 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
17443
17476
|
}
|
|
17444
17477
|
if (submitIntent.kind === 'submit-turn' && submitIntent.route === 'sticky-acp') {
|
|
17445
17478
|
await runDashboardChatMainSubmitIntent({
|
|
17446
|
-
submit: createDashboardChatMainTurnSubmit(
|
|
17479
|
+
submit: createDashboardChatMainTurnSubmit({
|
|
17480
|
+
...submitIntent,
|
|
17481
|
+
sessionId: submitIntent.sessionId ?? dashboardAcpSession.currentSessionId,
|
|
17482
|
+
}),
|
|
17447
17483
|
beforeExecute: (submit) => {
|
|
17448
17484
|
maybeEmitDashboardSubmitQuickPass({
|
|
17449
17485
|
submit,
|
|
@@ -19186,16 +19222,19 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
19186
19222
|
do {
|
|
19187
19223
|
const turnText = goalLoopUserText ?? tok.text;
|
|
19188
19224
|
await runDashboardChatMainSubmitIntent({
|
|
19189
|
-
submit: createDashboardChatMainTurnSubmit(
|
|
19190
|
-
submitIntent.kind === 'submit-turn' && submitIntent.route === 'plain' && goalLoopUserText === null
|
|
19191
|
-
|
|
19192
|
-
|
|
19193
|
-
|
|
19194
|
-
|
|
19195
|
-
|
|
19196
|
-
|
|
19197
|
-
|
|
19198
|
-
|
|
19225
|
+
submit: createDashboardChatMainTurnSubmit({
|
|
19226
|
+
...(submitIntent.kind === 'submit-turn' && submitIntent.route === 'plain' && goalLoopUserText === null
|
|
19227
|
+
? submitIntent
|
|
19228
|
+
: {
|
|
19229
|
+
kind: 'submit-turn' as const,
|
|
19230
|
+
source: { kind: 'keyboard' as const, surface: 'dashboard-chat-main' },
|
|
19231
|
+
text: turnText,
|
|
19232
|
+
route: 'plain' as const,
|
|
19233
|
+
}),
|
|
19234
|
+
sessionId: submitIntent.kind === 'submit-turn'
|
|
19235
|
+
? submitIntent.sessionId ?? dashboardAcpSession.currentSessionId
|
|
19236
|
+
: dashboardAcpSession.currentSessionId,
|
|
19237
|
+
}),
|
|
19199
19238
|
beforeExecute: (submit) => {
|
|
19200
19239
|
maybeEmitDashboardSubmitQuickPass({
|
|
19201
19240
|
submit,
|
|
@@ -19246,6 +19285,29 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
19246
19285
|
}
|
|
19247
19286
|
} while (goalLoopUserText !== null);
|
|
19248
19287
|
|
|
19288
|
+
// SESSION-RESUME-ISOLATED: 완료된 턴의 ACP 세션을 원장에 등록하고 active 로 지정한다.
|
|
19289
|
+
// 서버의 완료-채택(persistCompletedTuiAcpTurn)은 origin_session 메타가 실렸을 때만
|
|
19290
|
+
// 도는데, 격리 우주 첫 턴에는 origin 세션이 없어 메타가 안 실린다 — 여기가 그 갭을 닫는다.
|
|
19291
|
+
const completedAcpSessionId = dashboardAcpSession.currentSessionId;
|
|
19292
|
+
if (completedAcpSessionId) {
|
|
19293
|
+
acpSessionIdRef.current = completedAcpSessionId;
|
|
19294
|
+
// 원격 데몬 세션의 원장은 데몬이 소유한다. 로컬 등록은 세션마다 한 번만 한다.
|
|
19295
|
+
if (!daemonAttached && activeAcpSessionId !== completedAcpSessionId) {
|
|
19296
|
+
try {
|
|
19297
|
+
if (!adoptedAcpSessionIds.has(completedAcpSessionId)) {
|
|
19298
|
+
adoptChatSession(completedAcpSessionId, { source: 'tui', transport: 'acp' });
|
|
19299
|
+
adoptedAcpSessionIds.add(completedAcpSessionId);
|
|
19300
|
+
}
|
|
19301
|
+
markActiveChatSession(completedAcpSessionId);
|
|
19302
|
+
activeAcpSessionId = completedAcpSessionId;
|
|
19303
|
+
} catch (error) {
|
|
19304
|
+
// 등록 실패가 대화 흐름을 끊지 않되, 다음 턴 재시도 전 원인을 남긴다.
|
|
19305
|
+
debug.log('session.adopt', 'failed', { sessionId: completedAcpSessionId, error: String(error) });
|
|
19306
|
+
pushDebugLine(C.warning(`session adopt failed: ${String(error)}`));
|
|
19307
|
+
}
|
|
19308
|
+
}
|
|
19309
|
+
}
|
|
19310
|
+
|
|
19249
19311
|
continue; // stay in input mode after chat
|
|
19250
19312
|
} // end while (input mode loop)
|
|
19251
19313
|
// Disarm the prompt-repaint hook: textInput's drawAll
|
|
@@ -191,10 +191,15 @@ export async function runDashboardChatMainPlainTurn(
|
|
|
191
191
|
// 「사다리가 안 걸린 것」과 「걸렸는데 키가 딴 데로 간 것」을 ***원리상 못 갈랐다***.
|
|
192
192
|
// ⭐ 이 한 줄이 그 둘을 가른다 — 이 이벤트가 «있는데» typeahead 가 0이면 후자다.
|
|
193
193
|
// ⛔ 행동을 바꾸지 않는다(관측만). 처방은 `RFC-tui-mid-turn-input-queue-2026-08-19` §2 Q1.
|
|
194
|
+
const startedAtDate = new Date(deps.turnStartedAt);
|
|
195
|
+
const turnStartedAtIso = Number.isFinite(startedAtDate.getTime())
|
|
196
|
+
? startedAtDate.toISOString()
|
|
197
|
+
: undefined;
|
|
194
198
|
try {
|
|
195
199
|
deps.debugLog?.('chat-main.plain-turn', 'streaming-keys-attached', {
|
|
196
200
|
userTextBytes: Buffer.byteLength(userText ?? '', 'utf8'),
|
|
197
201
|
turnStartedAt: deps.turnStartedAt,
|
|
202
|
+
turnStartedAtIso,
|
|
198
203
|
});
|
|
199
204
|
} catch { /* fail-soft — 관측이 턴을 막지 않는다 */ }
|
|
200
205
|
let stage = 'streaming-keys-attached';
|
|
@@ -291,11 +296,32 @@ export async function runDashboardChatMainPlainTurn(
|
|
|
291
296
|
});
|
|
292
297
|
|
|
293
298
|
if (finishIfAborted()) return;
|
|
299
|
+
// TUI-FIRST-TURN-STALL — 09:48 표본에서 llm.stream consume-start 뒤
|
|
300
|
+
// first-token 이벤트가 0건이어서 「첫 토큰까지의 구간」을 잴 표면이 없었다.
|
|
301
|
+
// turnStartedAt 를 재사용해 첫 비어 있지 않은 text chunk 에 정확히 한 번
|
|
302
|
+
// chat.turn/first-token 을 남긴다. 도구 호출이 먼저 흘러도 text 가 도착해야
|
|
303
|
+
// 기록되므로 request→first-token 구간은 그대로 측정된다. ISO 시각 좌표는
|
|
304
|
+
// pty record 및 TUI-BOOT-TIME(src/index.ts) 부팅 계측과의 대응용이다.
|
|
305
|
+
let firstTokenLogged = false;
|
|
306
|
+
const logFirstToken = (chunk: string): void => {
|
|
307
|
+
if (firstTokenLogged || !chunk || abortCtrl.signal.aborted) return;
|
|
308
|
+
firstTokenLogged = true;
|
|
309
|
+
const firstTokenAt = Date.now();
|
|
310
|
+
try {
|
|
311
|
+
deps.debugLog?.('chat.turn', 'first-token', {
|
|
312
|
+
ms: firstTokenAt - deps.turnStartedAt,
|
|
313
|
+
turnStartedAt: deps.turnStartedAt,
|
|
314
|
+
turnStartedAtIso,
|
|
315
|
+
firstTokenAtIso: new Date(firstTokenAt).toISOString(),
|
|
316
|
+
});
|
|
317
|
+
} catch { /* fail-soft — 관측이 턴을 막지 않는다 */ }
|
|
318
|
+
};
|
|
294
319
|
const streamResult = await runDashboardPlainTurnStream({
|
|
295
320
|
userText,
|
|
296
321
|
abortCtrl,
|
|
297
322
|
acpSession: deps.acpSession,
|
|
298
323
|
onText: (chunk, accumulated) => {
|
|
324
|
+
logFirstToken(chunk);
|
|
299
325
|
turnStreamRuntime.onText(chunk, accumulated);
|
|
300
326
|
},
|
|
301
327
|
onToolCall: (call) => {
|
|
@@ -2490,6 +2490,12 @@ export function buildDashboardSlashRegistry(nowDeps?: ContextNowDeps, decideLedg
|
|
|
2490
2490
|
});
|
|
2491
2491
|
if (result.kind === 'launch') {
|
|
2492
2492
|
ctx.chatLines.push(ctx.accent(` ✅ 전제 검사 통과 — 골: ${result.goalFile}`));
|
|
2493
|
+
const { resolveObserveOnlyDecision } = await import('../../self-implement/observe-only.js');
|
|
2494
|
+
if (resolveObserveOnlyDecision().enabled) {
|
|
2495
|
+
ctx.chatLines.push(ctx.muted(' 관측 전용이라 런을 발사하지 않았습니다 · 코드도 안 바꿨습니다'));
|
|
2496
|
+
ctx.chatLines.push(ctx.muted(' 조회: elanous logs --category self-implement --since 10m'));
|
|
2497
|
+
return;
|
|
2498
|
+
}
|
|
2493
2499
|
// ⭐ 런을 «띄운다» — detached 라 이 TUI 를 닫아도 런은 산다.
|
|
2494
2500
|
// ⛔ spawn 을 여기서 새로 쓰지 않는다 — `launchDevGoalFileDetached` «한 자리»가 인자·env 를 갖는다.
|
|
2495
2501
|
// ⛔ 그리고 「띄웠다」와 「끝났다」는 다른 값이다 — 이 표면은 «띄운 것»까지만 말한다.
|
package/src/debug/log.ts
CHANGED
|
@@ -949,7 +949,7 @@ const SECRET_TEXT_RULES: readonly { readonly id: string; readonly re: RegExp; re
|
|
|
949
949
|
{ id: 'gcp-api-key', re: /\b(AIza)[0-9A-Za-z_-]{35}\b/g, to: '$1***' },
|
|
950
950
|
{ id: 'jwt', re: /\b(ey)[A-Za-z0-9_-]{10,}\.ey[A-Za-z0-9._\\/-]{10,}/g, to: '$1***' },
|
|
951
951
|
// gitleaks: generic-api-key 축소판 — `key = value` 형태.
|
|
952
|
-
{ id: 'generic-api-key', re: /\b(api[_-]?key|secret|password|passwd|token|credential)\b(\s*[:=]\s*)["']?[^\s"',;)]{4,}/gi, to: '$1$2***' },
|
|
952
|
+
{ id: 'generic-api-key', re: /\b(access[_-]?token|refresh[_-]?token|api[_-]?key|secret|password|passwd|token|credential)\b(\s*[:=]\s*)["']?[^\s"',;)]{4,}/gi, to: '$1$2***' },
|
|
953
953
|
// 환경변수 형태 `OPENROUTER_API_KEY=…` — 위 규칙은 `_API_KEY` 앞에 단어 경계가 없어 못 잡는다. 값에 글자가 있어야 한다(`MAX_TOKEN=4096` 은 그대로).
|
|
954
954
|
{ id: 'env-secret', re: /\b([A-Z][A-Z0-9]*_(?:[A-Z0-9]+_)*(?:API_KEY|TOKEN|SECRET|PASSWORD|ACCESS_KEY))(\s*[:=]\s*)["']?(?=[^\s"',;)]*[A-Za-z])[^\s"',;)]{8,}/g, to: '$1$2***' },
|
|
955
955
|
// BEDROCK-PROVIDER — `AWS_BEARER_TOKEN_BEDROCK=…` 은 이름이 TOKEN 으로 «끝나지» 않아 위 규칙이 못 잡는다.
|
|
@@ -21,12 +21,15 @@ export const RELEASE_STATUS_SPEC: LLMToolSpec = {
|
|
|
21
21
|
+ 'topic=cell: id 칸 하나의 상태·근거와 살핀 판.\n'
|
|
22
22
|
+ 'topic=schedule: 발행 이후 판의 컷·착지·발행·동결 일정.\n'
|
|
23
23
|
+ 'topic=ops: 도는 런·발행·늦은 스케줄·자리·열린 결정의 출처 있는 개관.\n'
|
|
24
|
-
+ 'topic 과 action 을 동시에
|
|
24
|
+
+ 'topic 과 action 을 동시에 쓰면 topic 을 우선하고 action 은 무시한다. topic 은 읽을 대상, action 은 단독 조회의 짧은 요약.\n'
|
|
25
|
+
+ 'topic=features 에서는 owner·status 로 칸을 필터링할 수 있다.\n'
|
|
25
26
|
+ '발행·버전·피처·체크리스트·칸·컷·일정·운영 상태 질문은 Grep·ListDir·파일 검색보다 이 도구를 먼저 부른다 — 그 답은 파일이 아니라 원장에 있다.',
|
|
26
27
|
parameters: { type: 'object', properties: {
|
|
27
28
|
version: { type: 'string', description: '조회할 판. 생략하면 개발 판과 다음 판.' },
|
|
28
29
|
topic: { type: 'string', enum: ['status', 'features', 'cell', 'schedule', 'ops'], description: '읽을 대상(기본 status).' },
|
|
29
30
|
action: { type: 'string', enum: ['schedule', 'blocked', 'landed'], description: '짧은 읽기 요약: 다음 컷 일정·막힌 칸·최근 24시간 병합 PR.' },
|
|
31
|
+
owner: { type: 'string', description: 'topic=features 때 담당 읽기 필터.' },
|
|
32
|
+
status: { type: 'string', description: 'topic=features 때 상태 읽기 필터.' },
|
|
30
33
|
id: { type: 'string', description: 'topic=cell 때 찾을 칸 id(필수).' },
|
|
31
34
|
}, required: [] },
|
|
32
35
|
};
|
|
@@ -122,10 +125,23 @@ function cellView(item: ChecklistItem) {
|
|
|
122
125
|
export function dispatchReleaseStatus(args: Record<string, unknown>, deps: ReleaseToolDeps = {}): { text: string; structured: Record<string, unknown> } {
|
|
123
126
|
const topic = args.topic === undefined ? 'status' : args.topic;
|
|
124
127
|
if (typeof topic !== 'string' || !['status', 'features', 'cell', 'schedule', 'ops'].includes(topic)) throw new Error('topic 은 status, features, cell, schedule, ops 중 하나');
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
+
const ignoredAction = args.topic !== undefined && args.action !== undefined ? args.action : undefined;
|
|
129
|
+
const withIgnoredAction = (result: { text: string; structured: Record<string, unknown> }) => ignoredAction === undefined
|
|
130
|
+
? result
|
|
131
|
+
: { ...result, text: `action=${String(ignoredAction)} 은 무시됨 — topic 과 같이 못 쓴다\n${result.text}` };
|
|
128
132
|
const requested = args.version;
|
|
133
|
+
if (topic === 'features') {
|
|
134
|
+
for (const key of ['owner', 'status'] as const) {
|
|
135
|
+
if (args[key] !== undefined && (typeof args[key] !== 'string' || !args[key].trim())) throw new Error(`${key} 값이 필요합니다`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const filters = topic === 'features' ? {
|
|
139
|
+
...(typeof args.owner === 'string' ? { owner: args.owner } : {}),
|
|
140
|
+
...(typeof args.status === 'string' ? { status: args.status } : {}),
|
|
141
|
+
} : {};
|
|
142
|
+
const hasFeatureFilters = Object.keys(filters).length > 0;
|
|
143
|
+
const featureItemMatches = (item: ChecklistItem) =>
|
|
144
|
+
(!('owner' in filters) || item.owner === filters.owner) && (!('status' in filters) || item.status === filters.status);
|
|
129
145
|
if (requested !== undefined && (typeof requested !== 'string' || !requested.trim())) throw new Error('판 이름이 필요합니다');
|
|
130
146
|
const checklist = deps.checklist ?? listChecklist;
|
|
131
147
|
if (topic === 'ops') {
|
|
@@ -133,10 +149,10 @@ export function dispatchReleaseStatus(args: Record<string, unknown>, deps: Relea
|
|
|
133
149
|
const kinds: ContextFact['kind'][] = ['run', 'release', 'schedule-late', 'seat', 'decision'];
|
|
134
150
|
const facts = answer.facts.filter(fact => kinds.includes(fact.kind));
|
|
135
151
|
debug.log('release.tool', 'status', { action: 'status', topic, version: requested ?? null });
|
|
136
|
-
return { text: facts.length ? facts.map(fact => JSON.stringify(fact)).join('\n') : '운영 개관: 관측된 사실 없음', structured: { facts } };
|
|
152
|
+
return withIgnoredAction({ text: facts.length ? facts.map(fact => JSON.stringify(fact)).join('\n') : '운영 개관: 관측된 사실 없음', structured: { facts } });
|
|
137
153
|
}
|
|
138
154
|
const dev = checklistDevVersion(deps.devVersion?.() ?? devVersion());
|
|
139
|
-
if (args.action !== undefined) {
|
|
155
|
+
if (ignoredAction === undefined && args.action !== undefined) {
|
|
140
156
|
const action = args.action;
|
|
141
157
|
if (typeof action !== 'string' || !['schedule', 'blocked', 'landed'].includes(action)) {
|
|
142
158
|
throw new Error('action 은 schedule, blocked, landed 중 하나여야 합니다');
|
|
@@ -178,12 +194,13 @@ export function dispatchReleaseStatus(args: Record<string, unknown>, deps: Relea
|
|
|
178
194
|
const target = (requested as string | undefined) ?? dev;
|
|
179
195
|
validateVersion(target);
|
|
180
196
|
const data = checklist(target);
|
|
181
|
-
const
|
|
182
|
-
const
|
|
197
|
+
const matchingItems = data.items.filter(featureItemMatches);
|
|
198
|
+
const counts = hasFeatureFilters ? summarizeChecklist({ ...data, items: matchingItems }) : summarizeChecklist(data);
|
|
199
|
+
const items = matchingItems.slice(0, 60).map(({ id, title, status, owner, priority }) =>
|
|
183
200
|
({ id, title: firstLine(title), status, owner: owner ?? null, priority: priority ?? null }));
|
|
184
|
-
const total =
|
|
201
|
+
const total = matchingItems.length;
|
|
185
202
|
result = { text: `${target} 판 피처 ${total}칸 · 초록 ${counts.green} · 노랑 ${counts.yellow} · 빨강 ${counts.red} · 끝 ${counts.done}\n${items.map(item => `${item.id} ${item.title} · ${item.status} · ${item.owner ?? '미배정'}`).join('\n')}${total > 60 ? `\n앞 60칸만 표시(전체 ${total}칸)` : ''}`,
|
|
186
|
-
structured: { version: target, items, counts: { green: counts.green, yellow: counts.yellow, red: counts.red, done: counts.done }, total, truncated: total > 60 } };
|
|
203
|
+
structured: { version: target, items, counts: { green: counts.green, yellow: counts.yellow, red: counts.red, done: counts.done }, total, truncated: total > 60, ...(hasFeatureFilters ? { filters } : {}) } };
|
|
187
204
|
} else if (topic === 'cell') {
|
|
188
205
|
const id = requiredString(args, 'id');
|
|
189
206
|
const searched = requested ? [requested as string] : [...new Set([dev, nextVersion(dev), checklist(dev).released].filter(Boolean))];
|
|
@@ -208,7 +225,7 @@ export function dispatchReleaseStatus(args: Record<string, unknown>, deps: Relea
|
|
|
208
225
|
publishAt: row.publishAt ?? null, freezeFrom: row.freezeFrom ?? null, freezeUntil: row.freezeUntil ?? null })) } };
|
|
209
226
|
}
|
|
210
227
|
debug.log('release.tool', 'status', { action: 'status', topic, version: requested ?? dev });
|
|
211
|
-
return result;
|
|
228
|
+
return withIgnoredAction(result);
|
|
212
229
|
}
|
|
213
230
|
const version = requested ? null : dev;
|
|
214
231
|
const versions = requested ? [requested as string] : [version!, nextVersion(version!)];
|
|
@@ -253,7 +270,7 @@ export function dispatchReleaseStatus(args: Record<string, unknown>, deps: Relea
|
|
|
253
270
|
: published.state === 'unreadable' ? `발행 판 ${published.version} · 발행 기록 못 읽음` : '발행 판 모름';
|
|
254
271
|
const structured = { releases: entries, latestRun: latest, published };
|
|
255
272
|
debug.log('release.tool', 'status', { action: 'status', topic, version: versions.join(',') });
|
|
256
|
-
return { text: `${lines.join('\n\n')}\n${cards.map(formatElanousCard).join('\n')}\n${publishedLine}`, structured };
|
|
273
|
+
return withIgnoredAction({ text: `${lines.join('\n\n')}\n${cards.map(formatElanousCard).join('\n')}\n${publishedLine}`, structured });
|
|
257
274
|
}
|
|
258
275
|
|
|
259
276
|
function requiredString(args: Record<string, unknown>, key: string): string {
|
|
@@ -169,6 +169,22 @@ export function recordInboundTurn(
|
|
|
169
169
|
finally { if (ownDb) db.close(); }
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
export interface SurfaceUtteranceQuery {
|
|
173
|
+
from: string;
|
|
174
|
+
to: string;
|
|
175
|
+
surface?: string;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Read every event in a half-open UTC interval without touching recall state. */
|
|
179
|
+
export function querySurfaceUtterances(db: Database, opts: SurfaceUtteranceQuery): SurfaceEventRow[] {
|
|
180
|
+
const where = ["julianday(e.ts) >= julianday(?)", "julianday(e.ts) < julianday(?)", "e.direction = 'inbound'", "e.kind IN ('utterance', 'qna')"];
|
|
181
|
+
const params: string[] = [opts.from, opts.to];
|
|
182
|
+
if (opts.surface) { where.push('e.surface = ?'); params.push(opts.surface); }
|
|
183
|
+
return db.prepare(
|
|
184
|
+
`SELECT e.* FROM events e WHERE ${where.join(' AND ')} ORDER BY julianday(e.ts) ASC, e.id ASC`,
|
|
185
|
+
).all(...params) as SurfaceEventRow[];
|
|
186
|
+
}
|
|
187
|
+
|
|
172
188
|
export interface QueryEventsOpts {
|
|
173
189
|
query?: string; // FTS MATCH (없으면 최근순 전체)
|
|
174
190
|
surface?: string;
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
// Resolution order:
|
|
16
16
|
// 1. Programmatic override set via `setElanousConfigDir(dir)` (incl.
|
|
17
17
|
// from the `--config-dir` CLI flag).
|
|
18
|
-
// 2. State-dir via `effectiveInstanceRoot()` (
|
|
19
|
-
// 3. `~/.elanous` for
|
|
18
|
+
// 2. State-dir via `effectiveInstanceRoot()` (explicit --test or parent stamp).
|
|
19
|
+
// 3. `~/.elanous` for both installed and source CLIs without an explicit test root.
|
|
20
20
|
|
|
21
21
|
import { homedir } from 'node:os';
|
|
22
22
|
import { resolve } from 'node:path';
|
package/src/fleet/host-doctor.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import { debug } from '../debug/log.js';
|
|
3
|
+
import type { ResourceView } from '../control-plane/ledger.js';
|
|
4
|
+
import type { MachineLedger, MachineFleetIntent } from '../machines/machine-ledger.js';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* 호스트 닥터 — 첫 조각(FLEET-DOCTOR-1 · 읽기 전용).
|
|
@@ -295,6 +297,66 @@ export interface HostDoctorOptions {
|
|
|
295
297
|
remote?: HostDoctorRemote;
|
|
296
298
|
expected?: HostDoctorExpected;
|
|
297
299
|
now?: () => Date;
|
|
300
|
+
/** HQ observations: null means the source could not be read; [] means a successful empty reading. */
|
|
301
|
+
membership?: {
|
|
302
|
+
intent: MachineLedger | null;
|
|
303
|
+
machines: readonly ResourceView[] | null;
|
|
304
|
+
parentHosts: readonly { host?: string; podPool?: string; approvalCapacity?: number }[] | null;
|
|
305
|
+
intentError?: string;
|
|
306
|
+
machinesError?: string;
|
|
307
|
+
parentHostsError?: string;
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function membershipCheck(id: string, status: HostDoctorCheckStatus, observed: string, expected: string): HostDoctorCheckResult {
|
|
312
|
+
return { id, status, observed, expected, fixKind: 'human', fix: 'compare Nexus machine registration, machines.yaml intent, and taskAgent.parentHost' };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Pure HQ comparison. A failed query is null (unmeasured); a successful empty result is a real missing host. */
|
|
316
|
+
export function checkHostMembership(host: string, platform: HostDoctorPlatform | null, membership: NonNullable<HostDoctorOptions['membership']>): HostDoctorCheckResult[] {
|
|
317
|
+
const intent: MachineFleetIntent | undefined = membership.intent?.machines.find((row) => row.fleet?.sshName === host)?.fleet;
|
|
318
|
+
const registryExpected = intent ? `live machine ${host} · role ${intent.role}` : `machines.yaml fleet intent for ${host}`;
|
|
319
|
+
let registry: HostDoctorCheckResult;
|
|
320
|
+
if (membership.intent === null || membership.machines === null) {
|
|
321
|
+
registry = membershipCheck('registry.member', 'unmeasured', membership.intent === null
|
|
322
|
+
? `intent ledger unavailable (repository checkout required)${membership.intentError ? `: ${membership.intentError}` : ''}`
|
|
323
|
+
: `Nexus machine query unavailable${membership.machinesError ? `: ${membership.machinesError}` : ''}`, registryExpected);
|
|
324
|
+
} else if (!intent) {
|
|
325
|
+
registry = membershipCheck('registry.member', 'fail', 'host absent from fleet intent', registryExpected);
|
|
326
|
+
} else {
|
|
327
|
+
// `machine` is the Nexus identity; `name` is a display label. A missing/expired record
|
|
328
|
+
// is a measured failure even when the remote platform probe did not run.
|
|
329
|
+
const rows = membership.machines.filter((row) => row.kind === 'machine' && row.machine === host && !row.expired);
|
|
330
|
+
const row = rows[0];
|
|
331
|
+
const role = row?.attrs.role;
|
|
332
|
+
if (!row) registry = membershipCheck('registry.member', 'fail', 'no live Nexus machine record', registryExpected);
|
|
333
|
+
else if (rows.length !== 1) registry = membershipCheck('registry.member', 'fail', `${rows.length} live machine records`, registryExpected);
|
|
334
|
+
else if (typeof role !== 'string') registry = membershipCheck('registry.member', 'unmeasured', 'machine attrs.role unavailable', registryExpected);
|
|
335
|
+
else if (role !== intent.role) registry = membershipCheck('registry.member', 'fail', `machine role ${role}`, registryExpected);
|
|
336
|
+
// Existence and role have been measured above; a pass still needs the platform probe.
|
|
337
|
+
else if (platform === null) registry = membershipCheck('registry.member', 'unmeasured', 'host platform unavailable', `intent platform ${intent.platform}`);
|
|
338
|
+
else if (platform !== intent.platform) registry = membershipCheck('registry.member', 'fail', `host platform ${platform}`, `intent platform ${intent.platform}`);
|
|
339
|
+
else registry = membershipCheck('registry.member', 'pass', `live machine ${host} · role ${role}`, registryExpected);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const parentExpected = intent ? `taskAgent.parentHost ${host} · ${intent.podPool} · cap ${intent.cap}` : `machines.yaml fleet intent for ${host}`;
|
|
343
|
+
let parent: HostDoctorCheckResult;
|
|
344
|
+
if (membership.intent === null || membership.parentHosts === null) {
|
|
345
|
+
parent = membershipCheck('parentHost.member', 'unmeasured', membership.intent === null
|
|
346
|
+
? `intent ledger unavailable (repository checkout required)${membership.intentError ? `: ${membership.intentError}` : ''}`
|
|
347
|
+
: `taskAgent.parentHost unavailable${membership.parentHostsError ? `: ${membership.parentHostsError}` : ''}`, parentExpected);
|
|
348
|
+
} else if (!intent) {
|
|
349
|
+
parent = membershipCheck('parentHost.member', 'fail', 'host absent from fleet intent', parentExpected);
|
|
350
|
+
} else {
|
|
351
|
+
const rows = membership.parentHosts.filter((row) => row.host === host);
|
|
352
|
+
const row = rows[0];
|
|
353
|
+
if (intent.cap === 0) parent = membershipCheck('parentHost.member', rows.length === 0 ? 'pass' : 'fail', rows.length === 0 ? 'disabled in both intent and parent config' : 'disabled host still in parent config', 'host absent from taskAgent.parentHost (cap 0)');
|
|
354
|
+
else if (!row) parent = membershipCheck('parentHost.member', 'fail', 'host absent from taskAgent.parentHost', parentExpected);
|
|
355
|
+
else if (rows.length !== 1 || row.podPool !== intent.podPool || row.approvalCapacity !== intent.cap) {
|
|
356
|
+
parent = membershipCheck('parentHost.member', 'fail', `host entries ${rows.length} · pool ${row.podPool ?? '(missing)'} · cap ${row.approvalCapacity ?? '(missing)'}`, parentExpected);
|
|
357
|
+
} else parent = membershipCheck('parentHost.member', 'pass', `host ${host} · pool ${row.podPool} · cap ${row.approvalCapacity}`, parentExpected);
|
|
358
|
+
}
|
|
359
|
+
return [registry, parent];
|
|
298
360
|
}
|
|
299
361
|
|
|
300
362
|
/** 원격 `uname -s` ⊕ `/proc/version` 의 microsoft 로 darwin|linux|wsl. 못 재면 null. */
|
|
@@ -344,9 +406,14 @@ export function runHostDoctor(options: HostDoctorOptions): HostDoctorReport {
|
|
|
344
406
|
const platform = reached ? detectHostDoctorPlatform(remote, host) : null;
|
|
345
407
|
const arch = reached ? detectHostDoctorArch(remote, host) : null;
|
|
346
408
|
const specs = HOST_DOCTOR_CHECKS.filter((spec) => spec.roles.includes(role));
|
|
347
|
-
const
|
|
409
|
+
const remoteChecks = reached
|
|
348
410
|
? specs.map((spec) => runCheck(spec, { host, remote, role, platform, arch, expected }))
|
|
349
411
|
: specs.map((spec) => unmeasuredBecauseUnreached(spec, { platform, arch, expected }));
|
|
412
|
+
const checks = [...remoteChecks, ...(role === 'worker' && options.membership ? checkHostMembership(host, platform, options.membership) : [])];
|
|
413
|
+
const mismatch = checks.filter((check) => (check.id === 'registry.member' || check.id === 'parentHost.member') && check.status === 'fail');
|
|
414
|
+
if (mismatch.length) debug.log('fleet.doctor', 'membership-mismatch', { host, checks: mismatch.map((check) => ({ id: check.id, observed: check.observed, expected: check.expected })) });
|
|
415
|
+
const membershipUnmeasured = checks.filter((check) => (check.id === 'registry.member' || check.id === 'parentHost.member') && check.status === 'unmeasured');
|
|
416
|
+
if (membershipUnmeasured.length) debug.log('fleet.doctor', 'membership-unmeasured', { host, checks: membershipUnmeasured.map((check) => ({ id: check.id, observed: check.observed })) });
|
|
350
417
|
const fail = checks.filter((check) => check.status === 'fail').map((check) => check.id);
|
|
351
418
|
const unmeasured = checks.filter((check) => check.status === 'unmeasured').map((check) => check.id);
|
|
352
419
|
const measuredAt = (options.now ?? (() => new Date()))().toISOString();
|
|
@@ -365,9 +432,13 @@ export function runHostDoctor(options: HostDoctorOptions): HostDoctorReport {
|
|
|
365
432
|
};
|
|
366
433
|
}
|
|
367
434
|
|
|
368
|
-
/** 사람 출력 한 줄씩 — «✓|✗|→ <id> <한 줄>»
|
|
435
|
+
/** 사람 출력 한 줄씩 — 기본은 «✓|✗|→ <id> <한 줄>»(node-setup.sh --check),
|
|
436
|
+
* membership 미측정은 눈에 보이는 «⚠ <id> unmeasured: <원인>»을 쓴다. */
|
|
369
437
|
export function formatHostDoctorLines(report: HostDoctorReport): string[] {
|
|
370
438
|
return report.checks.map((check) => {
|
|
439
|
+
if ((check.id === 'registry.member' || check.id === 'parentHost.member') && check.status === 'unmeasured') {
|
|
440
|
+
return `⚠ ${check.id} unmeasured: ${check.observed}`;
|
|
441
|
+
}
|
|
371
442
|
const mark = check.status === 'pass' ? '✓' : check.status === 'fail' ? '✗' : '→';
|
|
372
443
|
const detail = check.status === 'fail' ? `${check.observed} (expected: ${check.expected})` : check.observed;
|
|
373
444
|
return `${mark} ${check.id} ${detail}`;
|
|
@@ -26,6 +26,7 @@ import type { BashNode, NodeExecContext, WorkflowDeps } from '../workflow-runtim
|
|
|
26
26
|
import { releaseResumeAcceptedRegressions, releaseResumeGateExecutor, releaseResumeWaiver, type AcceptedRegression, type GateExecutorOverride } from '../../scripts/release-loop/resume.js';
|
|
27
27
|
|
|
28
28
|
type BashRun = WorkflowDeps['runBash'];
|
|
29
|
+
const DEFAULT_BASH_TIMEOUT_MS = 5 * 60 * 1_000;
|
|
29
30
|
|
|
30
31
|
export interface GraphRunState {
|
|
31
32
|
graphId: string;
|
|
@@ -878,7 +879,8 @@ export async function runGraph(path: string, options: GraphRunOptions = {}): Pro
|
|
|
878
879
|
env.ELANOUS_GRAPH_DRY_RUN = state.dryRun ? '1' : '0';
|
|
879
880
|
env.ELANOUS_GRAPH_DIR = dirname(resolve(path));
|
|
880
881
|
const ctx = { arguments: '', artifactsDir: dirname(statePath), outputs: {}, resolvedProvider: undefined, resolvedModel: undefined, toolPolicy: {}, env } as NodeExecContext;
|
|
881
|
-
const result = await executeBashNode({ id: current, type: 'bash', bash: state.dryRun ? command.dry_run_command! : command.command,
|
|
882
|
+
const result = await executeBashNode({ id: current, type: 'bash', bash: state.dryRun ? command.dry_run_command! : command.command,
|
|
883
|
+
idle_timeout: command.timeout_ms ?? DEFAULT_BASH_TIMEOUT_MS } as BashNode, ctx, { runBash } as WorkflowDeps);
|
|
882
884
|
output = result.output;
|
|
883
885
|
ok = result.ok;
|
|
884
886
|
exit = code;
|