elanous 0.2.7 → 0.2.9
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/bFI5EEwg7T6aG88ibX4Zq/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8630-cdfc61e82c653ffc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8894-e263b332208e3957.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9454-139040d37c79dbc9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-fb7c2d458f32fcad.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-35e2713597dd24d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-918b656f064577ba.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-624f7a2f89b60c8f.js → page-f972fc1c00a62e88.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-5a5fb385a4df0ffe.js → page-5f30e00693527ebd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-80497e93a878ddc5.js → page-7fbae173b3c103a7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/{page-7457f52683562161.js → page-3b4edcce364062ea.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-c5794f484c251f44.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-1130d8eefcd87e76.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-973996d584f008c1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-b7dcf5d48a854b76.js → page-016e2bda04eecb1f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-51480757e7d68b92.js → page-b4d6d9a3b56f2940.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-ec49a466121233ba.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-f72db356744ceade.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-a6fd9b50d1b85378.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-8b0402a46a749a3d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-07267f1391b96aba.js → page-037f83e2a1edc8e2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-55d50e9b0006a91d.js → page-adfbd2ba9598e59a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-46fedff57d4bec67.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-0cf318e9dcee2bbf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-899b327cb8b6845c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-6c1e6ba743a29dfe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/{page-a7e37af7d0399cb8.js → page-1a8e4807292f6b17.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-28deba6bcb834916.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-de68e2205794037d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-15e5a15db84c56aa.js → page-c71c33a573230672.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-a2e572e4511f2a16.js → page-0df30e0c7e50148a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-4fc56790fe0d2682.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-59df2aebb38a7c1e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-4647be5dbcbd0795.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-e025892bda74b743.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a0955de73b89f078.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-c1c95739c0bff3b8.js → page-369846d2921af584.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-72cbc1c8cdd0f65d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-71d18eec33b9c667.js → page-77b28bfb0996d5ea.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-3220c4934f49c6c2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-2c4e59079ee357ac.js → page-90670589b87abea8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-7df9243ed021a4cc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-5d422f38c052967e.js → page-4498c3853f4f688d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-35bedde6d885f08f.js +1 -0
- package/apps/pwa/out/_next/static/css/f92f63a5df0ba229.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +30 -29
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +30 -29
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +30 -29
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +27 -26
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +27 -26
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +27 -26
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +30 -29
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +30 -29
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +30 -29
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +30 -29
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +30 -29
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +30 -29
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +27 -26
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +30 -29
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +30 -29
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +27 -26
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +27 -26
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +30 -29
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +30 -29
- package/apps/pwa/out/ops/checklist/index.html +1 -0
- package/apps/pwa/out/ops/checklist/index.txt +30 -0
- package/apps/pwa/out/ops/release/index.html +1 -0
- package/apps/pwa/out/ops/release/index.txt +30 -0
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +28 -27
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +30 -29
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +27 -26
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +30 -29
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +30 -29
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +30 -29
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +30 -29
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +30 -29
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +27 -26
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +30 -29
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +30 -29
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +30 -29
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +30 -29
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +30 -29
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +30 -29
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +30 -29
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +30 -29
- package/graphs/release/recipes.yaml +3 -0
- package/graphs/release/release-loop.yaml +3 -1
- package/graphs/test-diet/nightly-audit.yaml +18 -0
- package/graphs/test-diet/recipes.yaml +12 -0
- package/graphs/test-diet/test-diet.yaml +22 -0
- package/package.json +2 -1
- package/persona-presets/_index.yaml +10 -0
- package/persona-presets/_schema.json +146 -0
- package/persona-presets/dana.yaml +26 -0
- package/persona-presets/haru.yaml +27 -0
- package/persona-presets/kai.yaml +27 -0
- package/persona-presets/leo.yaml +27 -0
- package/persona-presets/mira.yaml +30 -0
- package/persona-presets/nari.yaml +27 -0
- package/persona-presets/sora.yaml +27 -0
- package/persona-presets/yuna.yaml +27 -0
- package/plugins/doc-draft/graphs/run-step.ts +1 -1
- package/plugins/geo-check/graphs/run-step.ts +34 -10
- package/scripts/field-feed/deliver-node.ts +8 -3
- package/scripts/field-feed/lib.ts +2 -0
- package/scripts/install.sh +20 -11
- package/scripts/persona-presets/lint.ts +79 -0
- package/scripts/release-loop/checklist-gate-node.ts +2 -2
- package/scripts/release-loop/cutoff.ts +20 -2
- package/scripts/release-loop/export-check-node.ts +85 -0
- package/scripts/release-loop/gate-node.ts +45 -7
- package/scripts/release-loop/node-verdict.ts +8 -1
- package/scripts/release-loop/npm-publish-node.ts +10 -4
- package/scripts/release-loop/ops-upgrade-node.ts +11 -3
- package/scripts/release-loop/publish-node.ts +23 -1
- package/scripts/release-loop/shard-plan.ts +17 -0
- package/scripts/storyboard/storyboard.ts +167 -0
- package/scripts/test-diet/lib.ts +89 -0
- package/scripts/test-diet/node.ts +144 -0
- package/src/acp/auth-methods.ts +30 -0
- package/src/acp/server.ts +5 -5
- package/src/auth/setup-link-tokens.fixture.ts +19 -0
- package/src/auth/setup-link-tokens.ts +153 -0
- package/src/budget/unified-usage.ts +75 -0
- package/src/channel-bot-token.ts +37 -10
- package/src/channel-import/detect.ts +191 -0
- package/src/channel-import/run.ts +111 -0
- package/src/chat/index.ts +9 -9
- package/src/cli/config-test-sync.ts +4 -0
- package/src/cli/decisions-cli.ts +13 -3
- package/src/cli/doctor-bundle.ts +128 -0
- package/src/cli/doctor-cli.ts +36 -1
- package/src/cli/doctor-local-llm.ts +95 -0
- package/src/cli/import-cli.ts +60 -0
- package/src/cli/mcp-login.ts +13 -4
- package/src/cli/op-proxy-sync.ts +83 -0
- package/src/cli/persona-cli.ts +116 -0
- package/src/cli/pr-cli.ts +6 -2
- package/src/cli/release-cli.ts +49 -8
- package/src/cli/skills-cli.ts +173 -0
- package/src/cli/start-cli.ts +13 -4
- package/src/codex/setup.ts +15 -13
- package/src/dashboard/dashboard-status-lines.ts +45 -0
- package/src/dashboard/first-screen-band.ts +35 -0
- package/src/dashboard/index.ts +87 -14
- package/src/dashboard/provider-slash-runtime.ts +28 -11
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +12 -2
- package/src/dashboard/slash-runtime/help-from-registry.ts +4 -2
- package/src/dashboard/turn-stream-formatter.ts +1 -1
- package/src/decisions/decision-cards.ts +291 -0
- package/src/decisions/decision-ledger.ts +5 -2
- package/src/decisions/discord-decision-cards.ts +133 -0
- package/src/decisions/telegram-decision-cards.ts +100 -0
- package/src/directives/version-at.ts +77 -0
- package/src/discord.ts +17 -0
- package/src/domains/outbound-alert.ts +44 -1
- package/src/domains/repeated-failure.ts +1 -1
- package/src/error-report/report.ts +200 -0
- package/src/exec-requests/planner.ts +8 -2
- package/src/exec-requests/runner.ts +64 -24
- package/src/exec-requests/store.ts +2 -0
- package/src/field/field-feed-auto.ts +48 -0
- package/src/field/field-reel.ts +8 -1
- package/src/hooks/error-report.ts +210 -0
- package/src/hooks/receiver.ts +41 -1
- package/src/import/apply.ts +128 -0
- package/src/import/plan.ts +197 -0
- package/src/index.ts +98 -11
- package/src/input-core/text-entry.ts +13 -5
- package/src/intake-plane/seat-answer.ts +96 -0
- package/src/intake-plane/seat-doc-route.ts +17 -3
- package/src/maturity/feature-maturity.ts +2 -0
- package/src/nexus/api/consult-requests.ts +19 -8
- package/src/nexus/api/error-reports.ts +108 -0
- package/src/nexus/api/http-server.ts +41 -1
- package/src/nexus/api/meta-api.ts +27 -0
- package/src/nexus/api/operator.ts +61 -0
- package/src/nexus/api/ops-api.ts +139 -0
- package/src/nexus/api/ops-seats-sources.ts +65 -0
- package/src/nexus/api/ops-seats.ts +114 -0
- package/src/nexus/api/outbound-report.ts +7 -0
- package/src/nexus/api/public-routes.ts +14 -1
- package/src/nexus/api/running-runs-background.ts +68 -0
- package/src/nexus/api/running-runs-child.ts +6 -0
- package/src/nexus/api/setup-claim.ts +21 -0
- package/src/nexus/api/share-captures.ts +91 -0
- package/src/nexus/api/skill-problems.ts +56 -0
- package/src/nexus/api/terminals.ts +24 -8
- package/src/nexus/config/secrets/file-backend.ts +56 -18
- package/src/nexus/index.ts +25 -1
- package/src/nexus/setup-status.ts +7 -2
- package/src/oauth/browser-availability.ts +14 -0
- package/src/oauth/codex-quota-policy.ts +16 -0
- package/src/onboarding/continue-here.ts +13 -8
- package/src/onboarding/first-run-doctor.ts +54 -0
- package/src/onboarding/web-first.ts +8 -1
- package/src/onboarding.ts +66 -12
- package/src/persona/presets.ts +105 -0
- package/src/release-loop/checklist.ts +33 -69
- package/src/release-loop/feature-store.ts +327 -0
- package/src/release-loop/manifest.ts +59 -5
- package/src/seat-dispatch/ceo-commands.ts +65 -0
- package/src/self-dev/orchestrate.ts +18 -11
- package/src/shell-env-bootstrap.ts +44 -7
- package/src/skills/connect-claude-plugin.ts +142 -0
- package/src/skills/connect.ts +70 -0
- package/src/skills/index.ts +29 -1
- package/src/skills/repair.ts +124 -0
- package/src/skills/runner.ts +11 -2
- package/src/skills/tools/usage-slash.ts +8 -7
- package/src/skills/trigger-extract.ts +1 -1
- package/src/task-orchestrator/surfaces/pod-command-job.ts +16 -8
- package/src/telegram-commands.ts +29 -0
- package/src/telegram-run.ts +83 -13
- package/src/telegram.ts +34 -0
- package/src/thinking-line.ts +28 -20
- package/src/tui/chat/execution-badge.ts +1 -1
- package/src/user-config.ts +111 -13
- package/src/ux-sim/turn-states.ts +1 -1
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/4836-2eba6079f31b88d4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8067-f74bf14d67ad98f1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8519-f6b5d8a5833a7603.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8894-e92491a164192cc1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9740-67051141115ce775.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9804-64c02c3c127d5014.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d5ebde04844689e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-10ed24f413ed66e9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-211810b6c93ab66a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-cee86394de407aa6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-4ff8edab44196b8c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-92e4f44428d5e290.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-2a05dfe80c8bdf16.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-d9979633aa82f8e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-30533117beb53625.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-f7857826c2f97ad7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-9699b1786be6171d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-43dd772aee8af75f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-973ab7af59c718b7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a9f8d3865b91e436.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-dd107bf1c7d52a90.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-d42b99cdfb9f27c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-2764ff40dad5a995.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-c5f1152db116ff96.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-f933bca81ea38a6f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-a7aaffcf771662d8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-56adbe1a1f1379bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-a81f113f3ef89d72.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-fc27b00e20733146.js +0 -1
- package/apps/pwa/out/_next/static/css/f537bdb19c69306a.css +0 -1
- package/apps/pwa/out/_next/static/keajW7DDyghEs1y8oJe6T/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{keajW7DDyghEs1y8oJe6T → bFI5EEwg7T6aG88ibX4Zq}/_ssgManifest.js +0 -0
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// codex·elanous 자격 파일은 이 Job 에 없다. 격리 관문(호스트 넥서스에 닿으면 종료)은 기존 Job 과 같다.
|
|
6
6
|
// 명령 인자는 셸 문자열로 이어붙이지 않고 `"$@"` 로 따로 인용한다.
|
|
7
7
|
|
|
8
|
+
import { randomBytes } from 'node:crypto';
|
|
8
9
|
import { spawnSync } from 'node:child_process';
|
|
9
10
|
import { isAbsolute } from 'node:path';
|
|
10
11
|
import { getUserConfig } from '../../user-config.js';
|
|
@@ -66,6 +67,8 @@ if [ -d "$HOME/outbox" ]; then
|
|
|
66
67
|
fi`;
|
|
67
68
|
|
|
68
69
|
export interface PodCommandJobInput {
|
|
70
|
+
/** POD1 — this launch's own label; cleanup selects by it, never by name alone. */
|
|
71
|
+
launch?: string;
|
|
69
72
|
name: string;
|
|
70
73
|
namespace: string;
|
|
71
74
|
image: string;
|
|
@@ -122,13 +125,13 @@ export function podCommandJobManifest(o: PodCommandJobInput): Record<string, unk
|
|
|
122
125
|
return {
|
|
123
126
|
apiVersion: 'batch/v1',
|
|
124
127
|
kind: 'Job',
|
|
125
|
-
metadata: { name: o.name, namespace: o.namespace, labels: { 'elanous.substrate': 'pod', 'elanous.job': o.name, 'elanous.kind': 'command' } },
|
|
128
|
+
metadata: { name: o.name, namespace: o.namespace, labels: { 'elanous.substrate': 'pod', 'elanous.job': o.name, 'elanous.kind': 'command', ...(o.launch ? { 'elanous.launch': o.launch } : {}) } },
|
|
126
129
|
spec: {
|
|
127
130
|
backoffLimit: 0,
|
|
128
131
|
ttlSecondsAfterFinished: 7200,
|
|
129
132
|
activeDeadlineSeconds: o.deadlineSeconds,
|
|
130
133
|
template: {
|
|
131
|
-
metadata: { labels: { 'elanous.job': o.name, 'elanous.kind': 'command' } },
|
|
134
|
+
metadata: { labels: { 'elanous.job': o.name, 'elanous.kind': 'command', ...(o.launch ? { 'elanous.launch': o.launch } : {}) } },
|
|
132
135
|
spec: {
|
|
133
136
|
restartPolicy: 'Never',
|
|
134
137
|
securityContext: { runAsUser: 1000, fsGroup: 1000 },
|
|
@@ -166,6 +169,7 @@ export function commandJobSecret(o: {
|
|
|
166
169
|
skillEnvText: Readonly<Record<string, string>>;
|
|
167
170
|
grokAuth?: string;
|
|
168
171
|
ghToken?: string;
|
|
172
|
+
launch?: string;
|
|
169
173
|
}): Record<string, unknown> | null {
|
|
170
174
|
const stringData: Record<string, string> = {};
|
|
171
175
|
if (o.grokAuth !== undefined) stringData['grok-auth.json'] = o.grokAuth;
|
|
@@ -177,7 +181,7 @@ export function commandJobSecret(o: {
|
|
|
177
181
|
if (Object.keys(stringData).length === 0) return null;
|
|
178
182
|
return {
|
|
179
183
|
apiVersion: 'v1', kind: 'Secret', type: 'Opaque',
|
|
180
|
-
metadata: { name: `${o.name}-creds`, namespace: o.namespace, labels: { 'elanous.job': o.name } },
|
|
184
|
+
metadata: { name: `${o.name}-creds`, namespace: o.namespace, labels: { 'elanous.job': o.name, ...(o.launch ? { 'elanous.launch': o.launch } : {}) } },
|
|
181
185
|
stringData,
|
|
182
186
|
};
|
|
183
187
|
}
|
|
@@ -265,7 +269,11 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
265
269
|
const image = options.image ?? 'elanous-harness:local';
|
|
266
270
|
const repoUrl = options.repoUrl ?? 'https://github.com/ElanvitalAI/elanous';
|
|
267
271
|
const deadlineSeconds = options.deadlineSeconds ?? POD_COMMAND_DEADLINE_SECONDS;
|
|
268
|
-
|
|
272
|
+
// POD1 (10-01): two launches in the same moment got the same `si-cmd-<time>` and the failing one's cleanup
|
|
273
|
+
// deleted the other's credentials. The name now has a random tail, and cleanup selects this launch's label.
|
|
274
|
+
const launch = randomBytes(6).toString('hex');
|
|
275
|
+
const name = options.name ?? podJobName(`cmd-${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`);
|
|
276
|
+
const own = `elanous.job=${name},elanous.launch=${launch}`;
|
|
269
277
|
const env = options.env ?? process.env;
|
|
270
278
|
const hostMirror = (options.hostMirror ?? env.ELANOUS_POD_HOST_MIRROR
|
|
271
279
|
?? (options.configHostMirror ?? (() => getUserConfig().pod?.hostMirror))())?.trim() || undefined;
|
|
@@ -293,7 +301,7 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
293
301
|
const currentContext = member || options.kubectl ? null : baseKubectl(['config', 'current-context']);
|
|
294
302
|
const context = member?.context ?? (currentContext?.status === 0 ? currentContext.stdout.trim() : '');
|
|
295
303
|
const kubectl: Kubectl = (args, stdin) => baseKubectl(context ? ['--context', context, ...args] : [...args], stdin);
|
|
296
|
-
const cleanupSecret = () => { kubectl(['-n', namespace, 'delete', 'secret',
|
|
304
|
+
const cleanupSecret = () => { kubectl(['-n', namespace, 'delete', 'secret', '-l', own, '--ignore-not-found']); };
|
|
297
305
|
const artifactsDir = `${options.artifactsRoot ?? `${effectiveInstanceRoot()}/pod-artifacts`}/${name}`;
|
|
298
306
|
try {
|
|
299
307
|
if (!context && !options.kubectl) throw new Error('pod command: kubectl context 를 확인할 수 없다');
|
|
@@ -328,19 +336,19 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
|
|
|
328
336
|
if (synced?.imageRef) member = { ...member, imageRef: synced.imageRef };
|
|
329
337
|
}
|
|
330
338
|
const ghToken = options.clone ? (options.ghToken ?? defaultGhToken)() : undefined;
|
|
331
|
-
const secret = commandJobSecret({ name, namespace, skills, skillEnvText, ...(grokAuth !== undefined ? { grokAuth } : {}), ...(ghToken !== undefined ? { ghToken } : {}) });
|
|
339
|
+
const secret = commandJobSecret({ name, namespace, launch, skills, skillEnvText, ...(grokAuth !== undefined ? { grokAuth } : {}), ...(ghToken !== undefined ? { ghToken } : {}) });
|
|
332
340
|
if (secret) {
|
|
333
341
|
const applied = kubectl(['apply', '-f', '-'], JSON.stringify(secret));
|
|
334
342
|
if (applied.status !== 0) throw new Error(applied.stderr.trim() || 'pod command: Secret 적용 실패');
|
|
335
343
|
}
|
|
336
344
|
const manifest = podCommandJobManifest({
|
|
337
|
-
name, namespace, image: member?.imageRef ?? image,
|
|
345
|
+
name, namespace, launch, image: member?.imageRef ?? image,
|
|
338
346
|
...(member?.imageRef ? { imagePullPolicy: 'IfNotPresent' as const } : {}),
|
|
339
347
|
repoUrl, command, skills, ...(llm ? { llm } : {}), ...(options.clone ? { clone: true } : {}),
|
|
340
348
|
...(options.source ? { source: options.source } : {}), ...(hostMirror ? { hostMirror } : {}), ...(bunCache ? { bunCache } : {}), ...(options.memoryLimit ? { memoryLimit: options.memoryLimit } : {}), deadlineSeconds,
|
|
341
349
|
...(options.runId ? { runId: options.runId } : {}),
|
|
342
350
|
});
|
|
343
|
-
kubectl(['-n', namespace, 'delete', 'job',
|
|
351
|
+
kubectl(['-n', namespace, 'delete', 'job', '-l', own, '--ignore-not-found']);
|
|
344
352
|
const applied = kubectl(['apply', '-f', '-'], JSON.stringify(manifest));
|
|
345
353
|
if (applied.status !== 0) {
|
|
346
354
|
cleanupSecret();
|
package/src/telegram-commands.ts
CHANGED
|
@@ -762,6 +762,35 @@ export function defaultTelegramCommands(): TgSlashCommand[] {
|
|
|
762
762
|
});
|
|
763
763
|
},
|
|
764
764
|
},
|
|
765
|
+
{
|
|
766
|
+
name: 'decisions',
|
|
767
|
+
description: '열린 결정 목록 — 카드를 다시 보내 여기서 바로 정할 수 있습니다',
|
|
768
|
+
handler: async (_args, ctx) => {
|
|
769
|
+
const { telegramDecisionService } = await import('./decisions/telegram-decision-cards.js');
|
|
770
|
+
const service = telegramDecisionService();
|
|
771
|
+
if (!service) return '결정 카드가 이 봇에 연결되어 있지 않습니다.';
|
|
772
|
+
if (!service.isOwner(String(ctx.userId))) return '결정은 소유자만 볼 수 있습니다.';
|
|
773
|
+
// A group would show every member the cards (and route memos from them) — private chat only (TC review #22660).
|
|
774
|
+
if (ctx.chatId !== ctx.userId) return '결정은 개인 대화에서만 볼 수 있습니다.';
|
|
775
|
+
return service.listOpen(String(ctx.chatId));
|
|
776
|
+
},
|
|
777
|
+
},
|
|
778
|
+
...(['coo', 'cto', 'cmo', 'cxo'] as const).map((command) => ({
|
|
779
|
+
name: command,
|
|
780
|
+
description: `${command.toUpperCase()} 자리에 일을 넘깁니다 — /${command} <할 일> (조율 채널 ⊕ 자리 메시지함)`,
|
|
781
|
+
handler: async (args: string[], ctx: { chatId: number; userId: number }) => {
|
|
782
|
+
const [{ handleCeoSeatCommand }, { telegramDecisionOwner }, { runGh }, { getUserConfig }, { openMsgStore }] = await Promise.all([
|
|
783
|
+
import('./seat-dispatch/ceo-commands.js'), import('./decisions/telegram-decision-cards.js'), import('./decisions/decision-cards.js'),
|
|
784
|
+
import('./user-config.js'), import('./msg/msg-store.js'),
|
|
785
|
+
]);
|
|
786
|
+
const cfg = getUserConfig();
|
|
787
|
+
const replyTarget = (cfg.raw?.decisions as { replyGhPr?: unknown } | undefined)?.replyGhPr;
|
|
788
|
+
return handleCeoSeatCommand(command, args, ctx, {
|
|
789
|
+
ownerId: telegramDecisionOwner(cfg), replyTarget: typeof replyTarget === 'string' ? replyTarget : null, runGh,
|
|
790
|
+
append: (message) => { const store = openMsgStore(); try { return store.append(message); } finally { store.close(); } },
|
|
791
|
+
});
|
|
792
|
+
},
|
|
793
|
+
})),
|
|
765
794
|
{
|
|
766
795
|
name: 'work',
|
|
767
796
|
description: '글을 알맞은 곳(흡수·태스크·하니스)에 넣고 결과를 여기로 답합니다 — /work [absorb|tasks|graph] <글>',
|
package/src/telegram-run.ts
CHANGED
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
// - 워크플로 트리거는 core 의 `POST /v1/workflows/telegram-dispatch` 로 넘긴다(`telegram-dispatch-forward.ts`).
|
|
12
12
|
|
|
13
13
|
import { debug } from './debug/log.js';
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
15
|
+
import { randomUUID } from 'node:crypto';
|
|
16
|
+
import { dirname, join } from 'node:path';
|
|
17
|
+
import { effectiveInstanceRoot } from './instance/resolve.js';
|
|
14
18
|
import { getUserConfig } from './user-config.js';
|
|
15
19
|
import { resolveChannelBotToken } from './channel-bot-token.js';
|
|
16
20
|
import { forwardTelegramDispatch } from './telegram-dispatch-forward.js';
|
|
@@ -55,10 +59,10 @@ export function setTelegramPollLockForTesting(deps: typeof pollLockForTesting):
|
|
|
55
59
|
/** 폴러를 띄우고 핸들을 돌려준다(시험용으로 분리 — 대기는 `runTelegramPoller` 가 한다). */
|
|
56
60
|
export async function startTelegramPollers(): Promise<TelegramRunResult> {
|
|
57
61
|
const cfg = getUserConfig();
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
if (cfg.telegram.enabled === false) throw new Error('telegram run: telegram.enabled 가 꺼져 있다');
|
|
63
|
+
const resolved = resolveChannelBotToken('telegram', cfg);
|
|
64
|
+
if (!resolved.token) throw new Error(`telegram run: bot token 못 풂 — ${resolved.reason}`);
|
|
65
|
+
const mainToken = resolved.token;
|
|
62
66
|
if (cfg.telegram.poller !== 'standalone') {
|
|
63
67
|
console.warn('[telegram run] ⚠️ telegram.poller 가 standalone 이 아니다 — 넥서스도 같은 토큰을 폴링하려 한다. 토큰 잠금이 먼저 잡은 쪽만 띄운다.');
|
|
64
68
|
}
|
|
@@ -105,25 +109,91 @@ export async function startTelegramPollers(): Promise<TelegramRunResult> {
|
|
|
105
109
|
return { started, refusedBotIds, late };
|
|
106
110
|
}
|
|
107
111
|
|
|
108
|
-
|
|
109
|
-
|
|
112
|
+
function failurePath(): string { return join(effectiveInstanceRoot(), 'telegram', 'run-failures.json'); }
|
|
113
|
+
|
|
114
|
+
function failureState(): { count: number; alerted: boolean } {
|
|
115
|
+
const path = failurePath();
|
|
116
|
+
if (!existsSync(path)) return { count: 0, alerted: false };
|
|
117
|
+
const data: unknown = JSON.parse(readFileSync(path, 'utf8'));
|
|
118
|
+
if (!data || typeof data !== 'object' || !('count' in data) || !Number.isSafeInteger(data.count)
|
|
119
|
+
|| (data.count as number) < 0 || ('alerted' in data && typeof data.alerted !== 'boolean')) {
|
|
120
|
+
throw new Error(`invalid telegram run failures at ${path}`);
|
|
121
|
+
}
|
|
122
|
+
return { count: data.count as number, alerted: 'alerted' in data ? data.alerted as boolean : false };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function writeFailureState(count: number, alerted: boolean): void {
|
|
126
|
+
const path = failurePath();
|
|
127
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
128
|
+
const tmp = `${path}.${randomUUID()}.tmp`;
|
|
110
129
|
try {
|
|
111
|
-
|
|
112
|
-
|
|
130
|
+
writeFileSync(tmp, JSON.stringify({ count, alerted }), { mode: 0o600, flag: 'wx' });
|
|
131
|
+
renameSync(tmp, path);
|
|
132
|
+
} finally { if (existsSync(tmp)) unlinkSync(tmp); }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** SIGINT/SIGTERM 까지 돈다. Failure exit policy is tested without a live poller. */
|
|
136
|
+
export async function runTelegramPoller(deps: {
|
|
137
|
+
start?: typeof startTelegramPollers;
|
|
138
|
+
alert?: (text: string, kind: string) => boolean;
|
|
139
|
+
wait?: () => Promise<void>;
|
|
140
|
+
registerLogSink?: () => Promise<void>;
|
|
141
|
+
} = {}): Promise<void> {
|
|
142
|
+
try {
|
|
143
|
+
if (deps.registerLogSink) await deps.registerLogSink();
|
|
144
|
+
else {
|
|
145
|
+
const { registerStandaloneLogSink } = await import('./domains/standalone-log-sink.js');
|
|
146
|
+
await registerStandaloneLogSink('telegram-run');
|
|
147
|
+
}
|
|
113
148
|
} catch (e) {
|
|
114
149
|
console.error(`[telegram run] ⚠️ 로그 싱크 등록 실패(관측 유실 가능): ${e instanceof Error ? e.message : String(e)}`);
|
|
115
150
|
}
|
|
116
151
|
console.log('[telegram run] 토큰 잠금을 잡는 중 (토큰마다 최대 30초)…');
|
|
117
|
-
|
|
152
|
+
let result: TelegramRunResult;
|
|
153
|
+
try {
|
|
154
|
+
if (!deps.start) {
|
|
155
|
+
const [{ registerAllCloudBackends }, { selectBackendFromConfig }] = await Promise.all([
|
|
156
|
+
import('./nexus/config/secrets/register-cloud.js'), import('./nexus/config/secrets/registry.js'),
|
|
157
|
+
]);
|
|
158
|
+
registerAllCloudBackends();
|
|
159
|
+
selectBackendFromConfig();
|
|
160
|
+
}
|
|
161
|
+
result = await (deps.start ?? startTelegramPollers)();
|
|
162
|
+
if (result.started.length === 0) throw new Error('telegram run: 띄운 폴러가 없다');
|
|
163
|
+
} catch (error) {
|
|
164
|
+
const reason = error instanceof Error ? error.message : 'unknown start failure';
|
|
165
|
+
const previous = failureState();
|
|
166
|
+
const count = previous.count + 1;
|
|
167
|
+
writeFailureState(count, previous.alerted);
|
|
168
|
+
debug.log('telegram.run', 'start-failed', { count, reason });
|
|
169
|
+
if (count >= 3) {
|
|
170
|
+
if (!previous.alerted) {
|
|
171
|
+
try {
|
|
172
|
+
const alert = deps.alert ?? (await import('./domains/outbound-alert.js')).sendOutbound;
|
|
173
|
+
if (!alert(`텔레그램 폴러가 3번 연속 못 떴다 — ${reason}`, 'alert')) {
|
|
174
|
+
throw new Error('telegram run: outbound alert not delivered');
|
|
175
|
+
}
|
|
176
|
+
} catch (alertError) {
|
|
177
|
+
debug.log('telegram.run', 'alert-failed', { count, reason });
|
|
178
|
+
throw alertError;
|
|
179
|
+
}
|
|
180
|
+
writeFailureState(count, true);
|
|
181
|
+
debug.log('telegram.run', 'alerted', { count, reason });
|
|
182
|
+
}
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
const { started, refusedBotIds, late } = result;
|
|
188
|
+
const path = failurePath();
|
|
189
|
+
if (existsSync(path)) unlinkSync(path);
|
|
118
190
|
debug.log('telegram.run', 'started', { pid: process.pid, pollers: started.length, refusedBotIds });
|
|
119
191
|
for (const botId of refusedBotIds) {
|
|
120
192
|
console.warn(`[telegram run] 봇 ${botId}: 다른 프로세스가 폴링 중 — 15초마다 다시 잡으러 간다 (elanous logs --category telegram.run)`);
|
|
121
193
|
}
|
|
122
|
-
// 하나도 못 잡았으면 끝낸다(exit≠0) — 서비스 관리자(launchd KeepAlive·systemd Restart)가 다시 띄운다.
|
|
123
|
-
if (started.length === 0) throw new Error('telegram run: 띄운 폴러가 없다');
|
|
124
194
|
for (const { channel } of started) console.log(`[telegram run] 채널 '${channel.name}' 폴러 활성 (${channel.botToken.split(':')[0]})`);
|
|
125
195
|
console.log('[telegram run] Ctrl-C 로 멈춘다.');
|
|
126
|
-
await new Promise<void>((resolve) => {
|
|
196
|
+
await (deps.wait?.() ?? new Promise<void>((resolve) => {
|
|
127
197
|
const stop = (): void => {
|
|
128
198
|
process.off('SIGINT', stop);
|
|
129
199
|
process.off('SIGTERM', stop);
|
|
@@ -135,5 +205,5 @@ export async function runTelegramPoller(): Promise<void> {
|
|
|
135
205
|
};
|
|
136
206
|
process.on('SIGINT', stop);
|
|
137
207
|
process.on('SIGTERM', stop);
|
|
138
|
-
});
|
|
208
|
+
}));
|
|
139
209
|
}
|
package/src/telegram.ts
CHANGED
|
@@ -919,6 +919,24 @@ export class TelegramBot {
|
|
|
919
919
|
return undefined;
|
|
920
920
|
}
|
|
921
921
|
|
|
922
|
+
/** DEC-TG — replace a message's text AND its inline keyboard (empty rows remove the buttons). */
|
|
923
|
+
async editMessageWithKeyboard(
|
|
924
|
+
chatId: number,
|
|
925
|
+
messageId: number,
|
|
926
|
+
text: string,
|
|
927
|
+
buttons: Array<Array<{ text: string; data: string }>>,
|
|
928
|
+
): Promise<void> {
|
|
929
|
+
await this.throttledCall<unknown>(chatId, 'editMessageText', {
|
|
930
|
+
chat_id: chatId,
|
|
931
|
+
message_id: messageId,
|
|
932
|
+
text: text.length > this.maxChars ? `${text.slice(0, this.maxChars - 2)} …` : text,
|
|
933
|
+
reply_markup: { inline_keyboard: buttons.map((row) => row.map((b) => ({ text: b.text, callback_data: b.data.slice(0, 64) }))) },
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/** True while the long-poll loop runs — only a polling bot should push decision cards (nexus may hold a send-only bot). */
|
|
938
|
+
isPolling(): boolean { return this.running; }
|
|
939
|
+
|
|
922
940
|
/** T2-P6 — acknowledge a callback_query so Telegram clears the
|
|
923
941
|
* spinner next to the button the user tapped. Pass a `text` to
|
|
924
942
|
* flash a tiny toast on the user's device (≤200 chars). Returns
|
|
@@ -1156,6 +1174,8 @@ export class TelegramBot {
|
|
|
1156
1174
|
if (u.callback_query) {
|
|
1157
1175
|
const cq = u.callback_query;
|
|
1158
1176
|
if (!this.isOwnerAllowed(cq.from.id)) {
|
|
1177
|
+
// DEC-TG — a decision-card tap from someone else is the rehearsal's «refused» case; name it there too.
|
|
1178
|
+
if ((cq.data ?? '').startsWith('dec:')) debug.log('decisions.telegram', 'tap-refused', { platform: 'telegram', gate: 'owner-allowlist' }, { level: 'warn' });
|
|
1159
1179
|
debug.log('telegram.owner-gate', 'refused', {
|
|
1160
1180
|
kind: 'callback', reason: this.allowedUsers.size === 0 ? 'empty-allowlist' : 'not-allowed', userId: cq.from.id,
|
|
1161
1181
|
});
|
|
@@ -1425,6 +1445,9 @@ export class TelegramBot {
|
|
|
1425
1445
|
...reply, mimeType: 'video/mp4', caption: `현장 영상 · ${event} · ${result.seconds}초`,
|
|
1426
1446
|
});
|
|
1427
1447
|
if (!sent) throw new Error('sendDocument failed');
|
|
1448
|
+
if (result.feed?.started) {
|
|
1449
|
+
await this.sendMessage(ctx.chatId, `인스타 피드 초안을 만드는 중 · ${event} — 1분쯤 뒤 앱 «게시 대기»에서 고치고 «최종 게시»를 누르면 됩니다(누르기 전엔 아무 데도 안 올라갑니다).`, reply);
|
|
1450
|
+
}
|
|
1428
1451
|
} else {
|
|
1429
1452
|
await this.sendMessage(ctx.chatId, `현장 영상 실패 · ${event} · ${result.error ?? 'render failed'}`, reply);
|
|
1430
1453
|
}
|
|
@@ -2840,6 +2863,17 @@ export function botFromConfig(opts: BotFromConfigOpts): TelegramBot {
|
|
|
2840
2863
|
// poll loop requests `callback_query` from the first getUpdates —
|
|
2841
2864
|
// in-turn approval taps then arrive without a mid-turn delay.
|
|
2842
2865
|
botRef.ensureSurfaceHitl();
|
|
2866
|
+
// DEC-TG(대표 10-02) — decision cards on the MAIN bot only (other channel pollers carry other tokens). The ticker
|
|
2867
|
+
// itself sends only while this bot polls, so a send-only nexus bot never duplicates the standalone poller's cards.
|
|
2868
|
+
if (opts.userConfig.raw?.decisions === undefined || (opts.userConfig.raw.decisions as { cards?: unknown }).cards !== false) {
|
|
2869
|
+
const mainToken = resolveChannelBotToken('telegram', opts.userConfig)?.token;
|
|
2870
|
+
if (mainToken && botToken === mainToken) {
|
|
2871
|
+
const bot = botRef;
|
|
2872
|
+
void import('./decisions/telegram-decision-cards.js')
|
|
2873
|
+
.then(({ attachTelegramDecisionCards }) => { attachTelegramDecisionCards(bot, opts.userConfig); })
|
|
2874
|
+
.catch((err: unknown) => log(`[telegram] decision cards not attached: ${err instanceof Error ? err.message : String(err)}`));
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2843
2877
|
return botRef;
|
|
2844
2878
|
}
|
|
2845
2879
|
|
package/src/thinking-line.ts
CHANGED
|
@@ -156,6 +156,14 @@ function renderLine(
|
|
|
156
156
|
return `${applyColor(glyph, 'claude')} ${C.muted(message + '…')}${tail}`;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
function stoppedMessage(message: string, status: ThinkingStatus): string {
|
|
160
|
+
const taskName = message.replace(/^(?:Streaming|Thinking)(?=\b|…)(?:\s*…)?\s*/, '').trim();
|
|
161
|
+
const task = taskName ? `${taskName} ` : '';
|
|
162
|
+
if (status === 'completed') return `${task}완료`;
|
|
163
|
+
if (status === 'interrupted') return `${task}중단됨`;
|
|
164
|
+
return `${task}실패`;
|
|
165
|
+
}
|
|
166
|
+
|
|
159
167
|
export function startThinking(opts: StartThinkingOpts): ThinkingHandle {
|
|
160
168
|
let message = opts.message ?? 'Thinking';
|
|
161
169
|
let messageFactory: ((frame: number) => string) | null = null;
|
|
@@ -222,20 +230,19 @@ export function startThinking(opts: StartThinkingOpts): ThinkingHandle {
|
|
|
222
230
|
if (metrics.engine) detail.push(metrics.engine);
|
|
223
231
|
const tail = detail.length ? ' ' + C.muted('(' + detail.join(' · ') + ')') : '';
|
|
224
232
|
let finalText: string;
|
|
225
|
-
if (stopOpts?.finalText) {
|
|
233
|
+
if (stopOpts?.finalText !== undefined) {
|
|
226
234
|
finalText = stopOpts.finalText;
|
|
227
|
-
} else if (status === 'completed') {
|
|
228
|
-
// Green tick + verb kept visible (not muted) so it stands out.
|
|
229
|
-
const currentMessage = messageFactory ? messageFactory(frame) : message;
|
|
230
|
-
finalText = `${applyColor(FIGURES.TICK, 'success')} ${C.success(currentMessage)}${tail}`;
|
|
231
|
-
} else if (status === 'interrupted') {
|
|
232
|
-
const currentMessage = messageFactory ? messageFactory(frame) : message;
|
|
233
|
-
finalText = `${applyColor(FIGURES.CROSS, 'error')} ${C.warning(currentMessage + ' · interrupted')}${tail}`;
|
|
234
235
|
} else {
|
|
235
|
-
// failed
|
|
236
|
-
const err = stopOpts?.errorText ? ` — ${stopOpts.errorText}` : '';
|
|
237
236
|
const currentMessage = messageFactory ? messageFactory(frame) : message;
|
|
238
|
-
|
|
237
|
+
const label = stoppedMessage(currentMessage, status);
|
|
238
|
+
if (status === 'completed') {
|
|
239
|
+
finalText = `${applyColor(FIGURES.TICK, 'success')} ${C.success(label)}${tail}`;
|
|
240
|
+
} else if (status === 'interrupted') {
|
|
241
|
+
finalText = `${applyColor(FIGURES.CROSS, 'error')} ${C.warning(label)}${tail}`;
|
|
242
|
+
} else {
|
|
243
|
+
const err = stopOpts?.errorText ? ` — ${stopOpts.errorText}` : '';
|
|
244
|
+
finalText = `${applyColor(FIGURES.CROSS, 'error')} ${C.error(label + err)}${tail}`;
|
|
245
|
+
}
|
|
239
246
|
}
|
|
240
247
|
|
|
241
248
|
// Freeze at tail if sticky='tail' — the indicator was tracking
|
|
@@ -310,18 +317,19 @@ export function startPinnedThinking(opts: StartPinnedThinkingOpts): ThinkingHand
|
|
|
310
317
|
if (metrics.engine) detail.push(metrics.engine);
|
|
311
318
|
const tail = detail.length ? ' ' + C.muted('(' + detail.join(' · ') + ')') : '';
|
|
312
319
|
let finalText: string;
|
|
313
|
-
if (stopOpts?.finalText) {
|
|
320
|
+
if (stopOpts?.finalText !== undefined) {
|
|
314
321
|
finalText = stopOpts.finalText;
|
|
315
|
-
} else if (status === 'completed') {
|
|
316
|
-
const currentMessage = messageFactory ? messageFactory(frame) : message;
|
|
317
|
-
finalText = `${applyColor(FIGURES.TICK, 'success')} ${C.success(currentMessage)}${tail}`;
|
|
318
|
-
} else if (status === 'interrupted') {
|
|
319
|
-
const currentMessage = messageFactory ? messageFactory(frame) : message;
|
|
320
|
-
finalText = `${applyColor(FIGURES.CROSS, 'error')} ${C.warning(currentMessage + ' · interrupted')}${tail}`;
|
|
321
322
|
} else {
|
|
322
|
-
const err = stopOpts?.errorText ? ` — ${stopOpts.errorText}` : '';
|
|
323
323
|
const currentMessage = messageFactory ? messageFactory(frame) : message;
|
|
324
|
-
|
|
324
|
+
const label = stoppedMessage(currentMessage, status);
|
|
325
|
+
if (status === 'completed') {
|
|
326
|
+
finalText = `${applyColor(FIGURES.TICK, 'success')} ${C.success(label)}${tail}`;
|
|
327
|
+
} else if (status === 'interrupted') {
|
|
328
|
+
finalText = `${applyColor(FIGURES.CROSS, 'error')} ${C.warning(label)}${tail}`;
|
|
329
|
+
} else {
|
|
330
|
+
const err = stopOpts?.errorText ? ` — ${stopOpts.errorText}` : '';
|
|
331
|
+
finalText = `${applyColor(FIGURES.CROSS, 'error')} ${C.error(label + err)}${tail}`;
|
|
332
|
+
}
|
|
325
333
|
}
|
|
326
334
|
opts.target.current = finalText;
|
|
327
335
|
opts.onFrame();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// ── TUI TRUE-execution badge (self-cognition §1) ──────────────────────
|
|
2
2
|
//
|
|
3
3
|
// Resolves the compact engine/model badge for the dashboard chat
|
|
4
|
-
// completion line — `✔
|
|
4
|
+
// completion line — `✔ 완료 (19s · ↓ 200 tokens · 🧠 terra(high))`.
|
|
5
5
|
// The value is the model that ACTUALLY ran this turn (from the route
|
|
6
6
|
// decision recorded in the ACP bridge's getActiveModel), not the config
|
|
7
7
|
// original — so the "config=opus but codex/terra ran" illusion is
|
package/src/user-config.ts
CHANGED
|
@@ -437,11 +437,28 @@ const LLM_DEFAULTS: LLMConfig = { provider: 'auto' };
|
|
|
437
437
|
export type SkillSetName =
|
|
438
438
|
| 'claudecode' | 'opencode' | 'codex' | 'hermes' | 'openclaw' | 'custom';
|
|
439
439
|
|
|
440
|
+
export type SkillSourceKind = 'preset' | 'shared' | 'connected' | 'package';
|
|
441
|
+
|
|
442
|
+
/** One skill root elanous reads. `mode` is always `ref` today (the folder is read where it is). */
|
|
443
|
+
export interface SkillSource {
|
|
444
|
+
id: string;
|
|
445
|
+
path: string;
|
|
446
|
+
kind: SkillSourceKind;
|
|
447
|
+
enabled: boolean;
|
|
448
|
+
}
|
|
449
|
+
|
|
440
450
|
export interface SkillsConfig {
|
|
441
451
|
activeSet: SkillSetName;
|
|
442
452
|
dirs: string[];
|
|
443
453
|
/** Include the existing shared ~/.agents/skills directory. Default true; false opts out. */
|
|
444
454
|
includeSharedAgentSkills?: boolean;
|
|
455
|
+
/**
|
|
456
|
+
* EN5 — the one source list. On disk it holds only what `elanous connect` registered (kind `connected`, read in
|
|
457
|
+
* place, never copied); the preset (`activeSet`/`dirs`), the shared `~/.agents/skills` root and Claude package
|
|
458
|
+
* roots are derived at read time by `resolveSkillSources()`. The dev-only `connected: string[]` key (EN9, never
|
|
459
|
+
* released) is read and folded in here; the next save writes `sources` instead.
|
|
460
|
+
*/
|
|
461
|
+
sources?: SkillSource[];
|
|
445
462
|
/** Explicit skill-name allowlist. When set and non-empty, the skill
|
|
446
463
|
* index filters to this list — all other skills are hidden from
|
|
447
464
|
* routing, autocompletion, and `/run-skill`. Use when a project
|
|
@@ -517,34 +534,84 @@ export function bundledSkillsDir(packageRoot: string = dirname(import.meta.dir))
|
|
|
517
534
|
return join(packageRoot, 'skills');
|
|
518
535
|
}
|
|
519
536
|
|
|
520
|
-
|
|
521
|
-
cfg
|
|
522
|
-
|
|
523
|
-
|
|
537
|
+
function packageOptIn(cfg?: UserConfig): boolean {
|
|
538
|
+
try { return (cfg ?? getUserConfig()).skills.includeClaudePackageSkills === true; } catch { return false; }
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/** Stable id for a connected root: the agent it belongs to, else the folder (plugin) name. */
|
|
542
|
+
export function skillSourceId(path: string): string {
|
|
543
|
+
if (/[/\\]\.codex[/\\]skills\/?$/.test(path)) return 'codex';
|
|
544
|
+
if (/[/\\]\.claude[/\\]skills\/?$/.test(path)) return 'claude';
|
|
545
|
+
const parts = path.split(/[/\\]/).filter(Boolean);
|
|
546
|
+
const last = parts[parts.length - 1] === 'skills' ? parts[parts.length - 2] : parts[parts.length - 1];
|
|
547
|
+
return `plugin:${last ?? 'skills'}`;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/** Parse persisted `skills.sources` ⊕ the dev-only `skills.connected` key into connected sources (deduped by path). */
|
|
551
|
+
export function parsePersistedSkillSources(rawSources: unknown, legacyConnected: unknown): SkillSource[] | undefined {
|
|
552
|
+
const out: SkillSource[] = [];
|
|
553
|
+
const seen = new Set<string>();
|
|
554
|
+
const push = (source: SkillSource) => { if (!seen.has(source.path)) { seen.add(source.path); out.push(source); } };
|
|
555
|
+
if (Array.isArray(rawSources)) {
|
|
556
|
+
for (const row of rawSources) {
|
|
557
|
+
if (!row || typeof row !== 'object') continue;
|
|
558
|
+
const r = row as Record<string, unknown>;
|
|
559
|
+
if (typeof r.path !== 'string' || !r.path.trim()) continue;
|
|
560
|
+
const path = r.path.trim();
|
|
561
|
+
push({ id: typeof r.id === 'string' && r.id.trim() ? r.id.trim() : skillSourceId(path), path, kind: 'connected', enabled: r.enabled !== false });
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
if (Array.isArray(legacyConnected)) {
|
|
565
|
+
for (const path of legacyConnected) if (typeof path === 'string' && path.trim()) push({ id: skillSourceId(path.trim()), path: path.trim(), kind: 'connected', enabled: true });
|
|
566
|
+
}
|
|
567
|
+
return out.length ? out : undefined;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* ★ EN5 — the ONE function every reader uses (loader · setup checklist · connect · onboarding).
|
|
572
|
+
* Order is the historical order, so an old config resolves to exactly the old directory list:
|
|
573
|
+
* preset (activeSet/dirs) → shared ~/.agents/skills (only when it exists) → connected → Claude package roots (opt-in).
|
|
574
|
+
* Packaged `skills/` is appended by defaultSkillDirs, not listed here.
|
|
575
|
+
*/
|
|
576
|
+
export function resolveSkillSources(cfg?: UserConfig, opts?: DefaultSkillDirsOptions): SkillSource[] {
|
|
524
577
|
const claudecode = skillSetDir('claudecode')!; // = ~/.claude/skills (LOCAL_SKILLS_DIR)
|
|
525
|
-
|
|
578
|
+
const preset = (paths: string[], id: string): SkillSource[] => paths.map((path, i) => ({ id: paths.length > 1 ? `${id}:${i + 1}` : id, path, kind: 'preset', enabled: true }));
|
|
579
|
+
let sources: SkillSource[] = preset([claudecode], 'claudecode');
|
|
526
580
|
try {
|
|
527
581
|
const sk = (cfg ?? getUserConfig()).skills;
|
|
528
582
|
if (sk.activeSet !== 'custom') {
|
|
529
583
|
const d = skillSetDir(sk.activeSet);
|
|
530
|
-
if (d)
|
|
531
|
-
else if (sk.dirs.length > 0)
|
|
584
|
+
if (d) sources = preset([d], sk.activeSet);
|
|
585
|
+
else if (sk.dirs.length > 0) sources = preset([...sk.dirs], 'custom');
|
|
532
586
|
} else if (sk.dirs.length > 0) {
|
|
533
|
-
|
|
587
|
+
sources = preset([...sk.dirs], 'custom');
|
|
534
588
|
}
|
|
589
|
+
const has = (path: string) => sources.some((source) => source.path === path);
|
|
535
590
|
const shared = sharedAgentSkillRoots({
|
|
536
591
|
home: opts?.sharedAgentHome ?? REMOTE_HOME,
|
|
537
592
|
includeSharedAgentSkills: sk.includeSharedAgentSkills !== false,
|
|
538
593
|
});
|
|
539
594
|
try { debug.log('skills.roots', 'shared-agent', { found: shared.length }); }
|
|
540
595
|
catch { /* discovery remains fail-soft when observation is unavailable */ }
|
|
541
|
-
for (const
|
|
542
|
-
|
|
596
|
+
for (const path of shared) if (!has(path)) sources.push({ id: 'agents', path, kind: 'shared', enabled: true });
|
|
597
|
+
for (const source of sk.sources ?? []) if (!has(source.path)) sources.push({ ...source, kind: 'connected' });
|
|
598
|
+
if (sk.includeClaudePackageSkills === true) {
|
|
599
|
+
for (const path of appendClaudePackageSkillDirs([], opts?.pluginsRoot)) {
|
|
600
|
+
if (!has(path)) sources.push({ id: `package:${skillSourceId(path).replace(/^plugin:/, '')}`, path, kind: 'package', enabled: true });
|
|
601
|
+
}
|
|
543
602
|
}
|
|
544
|
-
base = sk.includeClaudePackageSkills === true
|
|
545
|
-
? appendClaudePackageSkillDirs(base, opts?.pluginsRoot)
|
|
546
|
-
: base;
|
|
547
603
|
} catch { /* fail-soft — config 없거나 파싱 실패 시 기본 경로 */ }
|
|
604
|
+
return sources;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
export function defaultSkillDirs(
|
|
608
|
+
cfg?: UserConfig,
|
|
609
|
+
opts?: DefaultSkillDirsOptions,
|
|
610
|
+
): string[] {
|
|
611
|
+
const sources = resolveSkillSources(cfg, opts);
|
|
612
|
+
const paths = sources.filter((source) => source.enabled).map((source) => source.path);
|
|
613
|
+
// Historical: the package opt-in path also collapsed duplicate user dirs; the plain path kept them (index dedupes later).
|
|
614
|
+
const base = sources.some((source) => source.kind === 'package') || packageOptIn(cfg) ? uniqueSkillDirsPreserveOrder(paths) : paths;
|
|
548
615
|
return appendBundledSkillsDir(base, opts?.bundledSkillsRoot);
|
|
549
616
|
}
|
|
550
617
|
|
|
@@ -1384,6 +1451,28 @@ export interface ChatCompactConfig {
|
|
|
1384
1451
|
archiveRetentionMb: number;
|
|
1385
1452
|
}
|
|
1386
1453
|
|
|
1454
|
+
/** ER1 — 액티브 덤퍼(오류 자동 보고). 기본 켬(첫 실행 안내 한 번 · 대표 10-01) · 신원(email/telegramUser)은 따로 «예»일 때만. */
|
|
1455
|
+
export interface ErrorReportsConfig {
|
|
1456
|
+
/** false = never send. Absent = on (the first-run notice says how to turn it off). */
|
|
1457
|
+
enabled?: boolean;
|
|
1458
|
+
/** true = also send email / telegramUser below with each report. Default false. */
|
|
1459
|
+
identity?: boolean;
|
|
1460
|
+
email?: string;
|
|
1461
|
+
telegramUser?: string;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
export function parseErrorReportsConfig(raw: unknown): ErrorReportsConfig | undefined {
|
|
1465
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return undefined;
|
|
1466
|
+
const r = raw as Record<string, unknown>;
|
|
1467
|
+
const out: ErrorReportsConfig = {};
|
|
1468
|
+
if (r.enabled === false || r.enabled === 'false') out.enabled = false;
|
|
1469
|
+
else if (r.enabled === true || r.enabled === 'true') out.enabled = true;
|
|
1470
|
+
if (r.identity === true || r.identity === 'true') out.identity = true;
|
|
1471
|
+
if (typeof r.email === 'string' && r.email.trim()) out.email = r.email.trim();
|
|
1472
|
+
if (typeof r.telegramUser === 'string' && r.telegramUser.trim()) out.telegramUser = r.telegramUser.trim();
|
|
1473
|
+
return Object.keys(out).length ? out : undefined;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1387
1476
|
export interface ChatConfig {
|
|
1388
1477
|
/** Short questions may receive a tool-free first reply. Default true. */
|
|
1389
1478
|
fastPath: boolean;
|
|
@@ -3373,6 +3462,8 @@ export interface UserConfig {
|
|
|
3373
3462
|
logs: LogsConfig;
|
|
3374
3463
|
shell: ShellConfig;
|
|
3375
3464
|
chat: ChatConfig;
|
|
3465
|
+
/** ER1 — optional so older fixtures stay valid; absent = defaults. */
|
|
3466
|
+
errorReports?: ErrorReportsConfig;
|
|
3376
3467
|
voice: VoiceConfig;
|
|
3377
3468
|
intake: IntakeConfig;
|
|
3378
3469
|
dashboard: DashboardConfig;
|
|
@@ -4419,6 +4510,7 @@ export function buildUserConfig(path: string = defaultPath()): UserConfig {
|
|
|
4419
4510
|
activeSet: normalizeSkillSet(sk.activeSet),
|
|
4420
4511
|
dirs: strArray(sk.dirs, skillsDefaults().dirs),
|
|
4421
4512
|
includeSharedAgentSkills: sk.includeSharedAgentSkills !== false,
|
|
4513
|
+
...(() => { const sources = parsePersistedSkillSources(sk.sources, sk.connected); return sources ? { sources } : {}; })(),
|
|
4422
4514
|
allow: strArray(sk.allow, []),
|
|
4423
4515
|
deny: strArray(sk.deny, []),
|
|
4424
4516
|
urlRouting: parseUrlRouting(sk.urlRouting),
|
|
@@ -4546,6 +4638,7 @@ export function buildUserConfig(path: string = defaultPath()): UserConfig {
|
|
|
4546
4638
|
// false required to short-circuit `buildDashboardOptionalToolSpecs`.
|
|
4547
4639
|
allowDashboardOptionalTools: sh.allowDashboardOptionalTools !== false,
|
|
4548
4640
|
},
|
|
4641
|
+
...((): { errorReports?: ErrorReportsConfig } => { const er = parseErrorReportsConfig(rawObj.errorReports); return er ? { errorReports: er } : {}; })(),
|
|
4549
4642
|
chat: {
|
|
4550
4643
|
fastPath: chat.fastPath === false || chat.fastPath === 'false' ? false : CHAT_DEFAULTS.fastPath,
|
|
4551
4644
|
conciseness: {
|
|
@@ -5590,6 +5683,10 @@ export function saveUserConfig(
|
|
|
5590
5683
|
skills: stripUndef({
|
|
5591
5684
|
activeSet: cfg.skills.activeSet,
|
|
5592
5685
|
dirs: [...cfg.skills.dirs],
|
|
5686
|
+
// EN5 — only connected roots are persisted (preset/shared/package are derived). The dev-only `connected` key is not written back.
|
|
5687
|
+
sources: cfg.skills.sources && cfg.skills.sources.some((source) => source.kind === 'connected')
|
|
5688
|
+
? cfg.skills.sources.filter((source) => source.kind === 'connected').map(({ id, path, enabled }) => ({ id, path, enabled }))
|
|
5689
|
+
: undefined,
|
|
5593
5690
|
// Omit allow/deny when empty to keep disk JSON minimal for the
|
|
5594
5691
|
// common "no filter" case.
|
|
5595
5692
|
allow: cfg.skills.allow && cfg.skills.allow.length > 0 ? [...cfg.skills.allow] : undefined,
|
|
@@ -5683,6 +5780,7 @@ export function saveUserConfig(
|
|
|
5683
5780
|
debug: cfg.debug,
|
|
5684
5781
|
logs: cfg.logs,
|
|
5685
5782
|
shell: cfg.shell,
|
|
5783
|
+
errorReports: cfg.errorReports ? stripUndef({ ...cfg.errorReports }) : undefined,
|
|
5686
5784
|
chat: stripUndef({
|
|
5687
5785
|
fastPath: cfg.chat.fastPath,
|
|
5688
5786
|
conciseness: stripUndef({
|
|
@@ -69,7 +69,7 @@ export function simLines(spec: TurnSimSpec): string[] {
|
|
|
69
69
|
case 'idle': return [];
|
|
70
70
|
case 'interrupted':
|
|
71
71
|
return [...head, ' 일부 답변이 여기까지 나왔습니다.', '',
|
|
72
|
-
' ✘
|
|
72
|
+
' ✘ 중단됨 (24s · ↓ 34 tokens)'];
|
|
73
73
|
case 'streaming-with-subagents': {
|
|
74
74
|
const n = Math.max(1, spec.children ?? 2);
|
|
75
75
|
return [...head, ...Array.from({ length: n }, (_, i) =>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"commit":"
|
|
1
|
+
{"commit":"8f3512a904cff53bb1595fffc091f7443a29c2c2"}
|