elanous 0.2.15 → 0.2.16
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/{1307-33ffcc49ab45123b.js → 1307-b46d45b705e8c9ae.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{9103-7a3ae7f60af3b99f.js → 9103-60fbef33e66f0a46.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-79c3ed84baaedd03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-e05a77df7e005b04.js → page-3cb52e5ea8fc2fa5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-a424ef80885d1743.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-5491c115b1f9a923.js → page-c1e20d7d338713f1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-b8c49f6b4fa2df91.js → page-482c5dbde699947c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-1e962b31d1f11081.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-976813c91d7394ea.js → page-8c2d9526164f5d09.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-ffff6d819fbc6e0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-a0e2a6d1bfb52fc5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-72d3e7ff35a8c954.js → page-c53598cfce6156f2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-60c7a8e1a4fe62ce.js → page-13a63953f53c6a23.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-2a52b316cb143d9c.js → page-5d3c8429d04f3831.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-c3ce483c8d5db370.js → page-3193bdc0bbab047a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-76270efaee638f17.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-96f074f6538e8a2c.js → page-6107ca8ddd5e3bdd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{layout-e97be5334d83a9f5.js → layout-dce686e582c23937.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-d3d53b146416e422.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-1a61c7ca1ceb3ae7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-955ec2da3be4c560.js → page-51f811fa119308b6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-9f88fbc15d9058d1.js → page-dacff46049e1557e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-c1343f9499d86f3f.js → page-4167348049166852.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-8739a2e0ae793130.js → not-found-de52e8fdf9095f7a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/{page-c9fc5706b8abaa11.js → page-36a7d994f4ca0299.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-d23b12bb92242558.js → page-ca3952cd78a83ca2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-5b042736aa113756.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-c7fea475488201a8.js → page-acb749f1e432bcf3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-cd78862707999911.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-54b9ef8afe6278fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-5efbf81124c474ae.js → page-1ebfe049c640e171.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-85f1a1fc0a82738c.js → page-57924393e3e4e539.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-9e0ce6c8129fa641.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-06ee266953d9c879.js → page-31fa1ce4c4aff9ae.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-153c2b3bcc89e8de.js → page-0f5cd57ca928f42b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-abe8e3b74bea1ffc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-4364ef9f4e7c7f0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/{page-0d6d23b40263a7ea.js → page-d28573d6792b4275.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-59f03fda76c03d22.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-d751de5f8062ff5e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/{page-f5597e894c02cd33.js → page-fe1759f94003b32b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/{page-0337fdf7fb5e37b6.js → page-d64c984e855c8c68.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-86914b0dee8ab7f8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/{page-8fcec4c9ea2e7f70.js → page-9afdee66b820c76e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-f7e631e9b4daf66e.js → page-d4aef67abf264874.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-1ea64acadd704ace.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-9817c7f9007466cc.js → page-3544f154c87b43ee.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-30c2073bfc21c004.js +1 -0
- package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +1 -0
- package/apps/pwa/out/_next/static/css/{04dabcbcc4bfa6db.css → 085673d826b98b62.css} +1 -1
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +15 -15
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +15 -15
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +15 -15
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +15 -15
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +15 -15
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +15 -15
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +15 -15
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +15 -15
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +15 -15
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +15 -15
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +15 -15
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +15 -15
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +15 -15
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +15 -15
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +15 -15
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +15 -15
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +15 -15
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +15 -15
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +15 -15
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +15 -15
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +15 -15
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +15 -15
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +15 -15
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +15 -15
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +15 -15
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +15 -15
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +16 -16
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +15 -15
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +15 -15
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +15 -15
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +15 -15
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +15 -15
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +15 -15
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +15 -15
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +15 -15
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +15 -15
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +15 -15
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +15 -15
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +15 -15
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +15 -15
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +15 -15
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +15 -15
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +15 -15
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +15 -15
- package/graphs/doc-gen/doc-gen.yaml +24 -0
- package/graphs/doc-gen/recipes.yaml +3 -0
- package/graphs/orchestrator/orchestrator.yaml +7 -0
- package/graphs/orchestrator/recipes.yaml +3 -0
- package/graphs/steward/recipes.yaml +2 -1
- package/package.json +1 -1
- package/scripts/ci-public-export-import-gate.ts +154 -0
- package/scripts/ci-test-interference-gate.ts +5 -1
- package/scripts/ci-typecheck-changed.ts +34 -15
- package/scripts/docs/doc-rot.ts +164 -0
- package/scripts/docs/generate-graph-anatomy.ts +131 -9
- package/scripts/docs-cli-check.ts +4 -4
- package/scripts/goals/goal-census.ts +343 -0
- package/scripts/measure/author-section-contribution.ts +84 -0
- package/scripts/release-loop/cut-branch.ts +43 -13
- package/scripts/release-loop/gate-node.ts +3 -1
- package/scripts/release-loop/vault-note-node.ts +19 -2
- package/scripts/rhythm/monthly.ts +97 -0
- package/src/acp/client.ts +17 -2
- package/src/agent-mission/driver.ts +51 -1
- package/src/agent-mission/supervisor-metrics-cli.ts +40 -0
- package/src/agent-mission/supervisor-metrics.ts +218 -0
- package/src/autopilot/track-agent-shadow.ts +2 -1
- package/src/briefing/brief-items.ts +191 -36
- package/src/budget/usage-outcomes.ts +204 -0
- package/src/cli/brief-cli.ts +73 -0
- package/src/cli/know-cli.ts +20 -0
- package/src/cli/logs-cli.ts +43 -1
- package/src/cli/pr-cli.ts +80 -12
- package/src/cli/release-cli.ts +11 -8
- package/src/cli/self-update.ts +200 -1
- package/src/cli/usage-cli.ts +26 -0
- package/src/discord-self-message.ts +9 -0
- package/src/flow-loop/split.ts +24 -0
- package/src/harness/harness-cli-command.ts +36 -2
- package/src/harness/harness-pod-dispatch.ts +25 -55
- package/src/harness/harness-seams.ts +13 -3
- package/src/harness/helper-repair.ts +139 -8
- package/src/harness/helper-scan.ts +13 -2
- package/src/harness/nested-elanous-policy.ts +114 -0
- package/src/hq/hq.ts +8 -2
- package/src/hq/lease.ts +5 -3
- package/src/hq/transfer.ts +185 -0
- package/src/index.ts +112 -6
- package/src/intake-plane/digest.ts +48 -26
- package/src/knowledge/know-find.ts +105 -0
- package/src/llm.ts +75 -19
- package/src/loops/orchestrator/finish-rate.ts +14 -3
- package/src/loops/orchestrator/tick.ts +96 -25
- package/src/maturity/bot-slash-catalog.ts +11 -2
- package/src/maturity/feature-maturity.ts +2 -2
- package/src/maturity/tui-slash-availability.ts +9 -0
- package/src/oauth/overload-failover-launch.ts +75 -0
- package/src/oauth/overload-failover.ts +137 -0
- package/src/release-loop/checklist.ts +10 -5
- package/src/release-loop/feature-store.ts +10 -9
- package/src/release-loop/placement.ts +17 -8
- package/src/seat-dispatch/seat-ask.ts +33 -6
- package/src/seat-loop/seat-loop.ts +90 -3
- package/src/self-dev/ask-launch-flow.ts +22 -1
- package/src/self-dev/authored-goal-trace.ts +64 -0
- package/src/self-dev/dev-cli.ts +19 -1
- package/src/self-dev/dev-pipeline.ts +6 -5
- package/src/self-dev/entrance-registry.ts +10 -6
- package/src/self-dev/old-door.ts +73 -0
- package/src/self-implement/app-api-budget.ts +166 -0
- package/src/self-implement/budget-gate.ts +7 -5
- package/src/self-implement/follow-up-goals.ts +289 -0
- package/src/self-implement/goal-author.ts +19 -13
- package/src/self-implement/heal-triage.ts +44 -0
- package/src/self-implement/orchestrator.ts +138 -7
- package/src/self-implement/run-ledger.ts +4 -0
- package/src/self-implement/seams.ts +44 -15
- package/src/session-runtime/index.ts +2 -0
- package/src/session-runtime/retry-api.ts +87 -0
- package/src/session-runtime/retry-policy.ts +107 -6
- package/src/steward/working-backwards.ts +16 -1
- package/src/task-cards/card-cli.ts +25 -0
- package/src/task-cards/card-store.ts +5 -3
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +4 -3
- package/src/task-orchestrator/surfaces/pod-ledger-prod-sink.ts +75 -0
- package/src/task-orchestrator/surfaces/pod-pool.ts +14 -5
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +37 -1
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +19 -10
- package/src/task-orchestrator/surfaces/self-implement.ts +2 -0
- package/src/telegram-commands.ts +5 -0
- package/src/telegram.ts +47 -0
- package/src/user-config.ts +36 -3
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-0e2c57fe6add4b2a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-f544338193a9a762.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-96398e364fbd4548.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-28b9448091776c06.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-1d4901eb7c2d8822.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-13c7b53e0c2519a9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-65e484302921a932.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-34d08facaf08f556.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8d47f45a0dc044ac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-e2ee2896811e98d0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-83b19dae8619de05.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-da6c08aae0f92768.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-87a11e6a05f15199.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-7d4182964407c09c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-7e8c39241f8eb6d0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-008c1a96618a4887.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-73735b64bfe1ae67.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-4881b8f9f63694b4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-97f9f43172a3a90e.js +0 -1
- package/apps/pwa/out/_next/static/css/1f7c958c41ffa3b9.css +0 -1
- package/src/launch-head/seat-goal-queue-lock.fixture.ts +0 -15
- package/src/launch-head/seat-goal-queue.ts +0 -198
- /package/apps/pwa/out/_next/static/{LZcstaO9_NXypPFmujolv → rv9cierV0jSOvDBm1g0XB}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{LZcstaO9_NXypPFmujolv → rv9cierV0jSOvDBm1g0XB}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { effectiveInstanceRoot } from '../../src/instance/resolve.js';
|
|
5
|
+
import { listSchedules } from '../../src/release-loop/release-schedule.js';
|
|
6
|
+
import { listChecklist } from '../../src/release-loop/checklist.js';
|
|
7
|
+
|
|
8
|
+
export type MonthlyDeps = {
|
|
9
|
+
now?: () => Date;
|
|
10
|
+
root?: string;
|
|
11
|
+
landings?: (window: { from: Date; to: Date }) => Promise<unknown[]>;
|
|
12
|
+
decisions?: () => Promise<unknown[]>;
|
|
13
|
+
schedules?: () => unknown[];
|
|
14
|
+
weeklyDir?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export async function runMonthly(deps: MonthlyDeps = {}) {
|
|
18
|
+
if (process.env.NODE_ENV === 'test' && (!deps.root || !deps.landings || !deps.decisions || !deps.schedules)) {
|
|
19
|
+
throw new Error('시험에서 월간 원장 수집에는 root·landings·decisions·schedules 주입 필요');
|
|
20
|
+
}
|
|
21
|
+
const now = (deps.now ?? (() => new Date()))();
|
|
22
|
+
const kst = new Date(now.getTime() + 9 * 3_600_000);
|
|
23
|
+
const year = kst.getUTCFullYear();
|
|
24
|
+
const month = kst.getUTCMonth();
|
|
25
|
+
const from = new Date(Date.UTC(year, month - 1, 1) - 9 * 3_600_000);
|
|
26
|
+
const to = new Date(Date.UTC(year, month, 1) - 9 * 3_600_000);
|
|
27
|
+
const key = new Date(from.getTime() + 9 * 3_600_000).toISOString().slice(0, 7);
|
|
28
|
+
const root = deps.root ?? effectiveInstanceRoot();
|
|
29
|
+
const file = join(root, 'rhythm', 'monthly', `${key}.md`);
|
|
30
|
+
if (existsSync(file)) return { file, status: '이미 있음' as const };
|
|
31
|
+
|
|
32
|
+
const safe = async <T>(read: () => T | Promise<T>): Promise<{ value: T; error?: never } | { error: string; value?: never }> => {
|
|
33
|
+
try { return { value: await read() }; }
|
|
34
|
+
catch (error) { return { error: (error instanceof Error ? error.message : String(error)).split(/\r?\n/, 1)[0]!.slice(0, 200) }; }
|
|
35
|
+
};
|
|
36
|
+
const landings = await safe(async () => (await (deps.landings ?? (async window => (await import('./daily.js')).collectLandings(now, window)))({ from, to })).length);
|
|
37
|
+
const releases = await safe(() => {
|
|
38
|
+
const schedules = deps.schedules ? deps.schedules() : listSchedules(root);
|
|
39
|
+
return schedules.map(value => {
|
|
40
|
+
if (!value || typeof value !== 'object' || !('cutAt' in value) || typeof value.cutAt !== 'string'
|
|
41
|
+
|| !('version' in value) || typeof value.version !== 'string' || !Number.isFinite(Date.parse(value.cutAt))) {
|
|
42
|
+
throw new Error('판 일정 형식 오류');
|
|
43
|
+
}
|
|
44
|
+
return { version: value.version as string, cutAt: value.cutAt as string };
|
|
45
|
+
}).filter(schedule => Date.parse(schedule.cutAt) >= from.getTime() && Date.parse(schedule.cutAt) < to.getTime());
|
|
46
|
+
});
|
|
47
|
+
const checklist = await safe(() => {
|
|
48
|
+
if (deps.schedules || releases.error !== undefined) return [];
|
|
49
|
+
return releases.value.map(schedule => ({ version: schedule.version, items: listChecklist(schedule.version, root).items }));
|
|
50
|
+
});
|
|
51
|
+
const decisions = await safe(async () => (await (deps.decisions ?? (async () => (await import('./daily.js')).collectDecisions()))()).length);
|
|
52
|
+
const weeks = await safe(() => readdirSync(deps.weeklyDir ?? join(root, 'rhythm', 'weekly'), { withFileTypes: true })
|
|
53
|
+
.filter(entry => entry.isFile()).map(entry => entry.name).filter(name => {
|
|
54
|
+
const match = /^(\d{4})-W(\d{2})\.md$/.exec(name);
|
|
55
|
+
if (!match) return false;
|
|
56
|
+
const jan4 = Date.UTC(Number(match[1]), 0, 4);
|
|
57
|
+
const monday = jan4 - ((new Date(jan4).getUTCDay() + 6) % 7) * 86_400_000 + (Number(match[2]) - 1) * 7 * 86_400_000;
|
|
58
|
+
const start = Date.UTC(year, month - 1, 1);
|
|
59
|
+
const end = Date.UTC(year, month, 1);
|
|
60
|
+
return Number(match[2]) >= 1 && Number(match[2]) <= 53 && monday < end && monday + 7 * 86_400_000 > start;
|
|
61
|
+
}).map(name => name.slice(0, -3)).sort());
|
|
62
|
+
const show = <T>(section: { value: T; error?: never } | { error: string; value?: never }, format: (value: T) => string) =>
|
|
63
|
+
section.error !== undefined ? `못 읽음 · ${section.error}` : format(section.value as T);
|
|
64
|
+
const releaseText = show(releases, values => values.length ? `판 ${values.length}건\n${values.map(schedule => {
|
|
65
|
+
const items = checklist.error === undefined ? checklist.value.find(row => row.version === schedule.version)?.items : undefined;
|
|
66
|
+
const progress = items ? ` · green ${items.filter(item => item.status === 'green').length}/${items.length}` : '';
|
|
67
|
+
return `- ${schedule.version} · ${new Date(Date.parse(schedule.cutAt) + 9 * 3_600_000).toISOString().slice(0, 10)}${progress}`;
|
|
68
|
+
}).join('\n')}${checklist.error !== undefined ? `\n체크리스트 못 읽음 · ${checklist.error}` : ''}` : '판 0건');
|
|
69
|
+
const landingText = show(landings, count => `착지 ${count}건`);
|
|
70
|
+
const decisionText = show(decisions, count => `결정 대기 ${count}건`);
|
|
71
|
+
const weekText = show(weeks, keys => keys.length ? keys.map(week => `- ${week}`).join('\n') : '주간 리뷰 0건');
|
|
72
|
+
const questions = [
|
|
73
|
+
landings.error !== undefined || releases.error !== undefined
|
|
74
|
+
? '착지와 판 이력을 확인하면 다음 달에 점검할 거리는 얼마인가?'
|
|
75
|
+
: `착지 ${landings.value}건과 판 ${releases.value.length}건을 바탕으로 다음 달에 점검할 거리는 얼마인가?`,
|
|
76
|
+
decisions.error !== undefined || weeks.error !== undefined
|
|
77
|
+
? '결정 대기와 주간 리뷰를 확인하면 다음 달 첫 우선순위는 무엇인가?'
|
|
78
|
+
: `결정 대기 ${decisions.value}건과 주간 리뷰 ${weeks.value.length}개의 신호로 다음 달 첫 우선순위는 무엇인가?`,
|
|
79
|
+
];
|
|
80
|
+
const markdown = [
|
|
81
|
+
`# 월간 미팅 — ${key} (KST)`,
|
|
82
|
+
`## S 지난달\n${landingText}\n${decisionText}\n주간 리뷰\n${weekText}`,
|
|
83
|
+
`## C 로드맵 거리\n기존 로드맵 기준(현행 목표 여부 미확인): 10-28 출시 · 11월 기업용\n판 이력\n${releaseText}`,
|
|
84
|
+
`## Q 다음 달 방향\n${questions.map((question, index) => `${index + 1}. ${question}`).join('\n')}`,
|
|
85
|
+
'## A 다음 달 첫 주\n- 판 이력과 주간 리뷰를 대조해 다음 달에 확인할 첫 칸을 고른다.\n- 열린 결정을 확인해 첫 주의 방향을 정한다.',
|
|
86
|
+
].join('\n\n') + '\n';
|
|
87
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
88
|
+
try { writeFileSync(file, markdown, { flag: 'wx' }); }
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (error && typeof error === 'object' && 'code' in error && error.code === 'EEXIST') return { file, status: '이미 있음' as const };
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
return { file, status: 'created' as const, markdown };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (import.meta.main) runMonthly().then(result => console.log(`${result.status}: ${result.file}`))
|
|
97
|
+
.catch(error => { console.error(`rhythm-monthly: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; });
|
package/src/acp/client.ts
CHANGED
|
@@ -302,7 +302,7 @@ const ACP_FORCED_BILLING_ENV_BY_BACKEND: Readonly<Record<string, Readonly<Record
|
|
|
302
302
|
};
|
|
303
303
|
|
|
304
304
|
function scrubAcpBillingEnv(spec: AcpBackendSpec, env: NodeJS.ProcessEnv): AcpBillingEnvScrubObservation {
|
|
305
|
-
const billingEnvScrubEnabled = getUserConfig().acp.scrubBillingEnv !== false;
|
|
305
|
+
const billingEnvScrubEnabled = spec.id === 'claude' || getUserConfig().acp.scrubBillingEnv !== false;
|
|
306
306
|
const scrubbedBillingEnv: string[] = [];
|
|
307
307
|
const forcedBillingEnv: string[] = [];
|
|
308
308
|
if (billingEnvScrubEnabled) {
|
|
@@ -378,6 +378,8 @@ export interface AcpAgentOpts {
|
|
|
378
378
|
/** Extra env vars to pass to the subprocess. Inherits from
|
|
379
379
|
* process.env unless overridden. */
|
|
380
380
|
env?: Record<string, string>;
|
|
381
|
+
/** Test seam: replace the child process without launching Claude. */
|
|
382
|
+
spawnChild?: typeof spawn;
|
|
381
383
|
/** Session-scoped Codex app-server configuration arguments. Ignored by
|
|
382
384
|
* non-Codex ACP backends. */
|
|
383
385
|
codexArgs?: readonly string[];
|
|
@@ -419,6 +421,7 @@ export class AcpAgent {
|
|
|
419
421
|
private readonly spec: AcpBackendSpec;
|
|
420
422
|
private readonly cwd: string;
|
|
421
423
|
private readonly env: NodeJS.ProcessEnv;
|
|
424
|
+
private readonly spawnChild: typeof spawn;
|
|
422
425
|
private readonly billingEnvScrub: AcpBillingEnvScrubObservation;
|
|
423
426
|
private readonly log: (msg: string) => void;
|
|
424
427
|
private readonly onCapabilities: ((capabilities: ElanousCapabilities) => void) | undefined;
|
|
@@ -461,6 +464,7 @@ export class AcpAgent {
|
|
|
461
464
|
}
|
|
462
465
|
this.billingEnvScrub = scrubAcpBillingEnv(this.spec, cleanEnv);
|
|
463
466
|
this.env = cleanEnv;
|
|
467
|
+
this.spawnChild = opts.spawnChild ?? spawn;
|
|
464
468
|
this.log = opts.log ?? ((m) => console.error(`[acp:${this.spec.id}] ${m}`));
|
|
465
469
|
this.onCapabilities = opts.onCapabilities;
|
|
466
470
|
this.permissionApprover = opts.permissionApprover ?? null;
|
|
@@ -501,7 +505,18 @@ export class AcpAgent {
|
|
|
501
505
|
throw new Error(`acp spawn: cwd does not exist: ${this.cwd}`);
|
|
502
506
|
}
|
|
503
507
|
|
|
504
|
-
|
|
508
|
+
if (this.spec.id === 'claude') {
|
|
509
|
+
const remaining = (claudeBackend.scrubEnv ?? []).filter(key => key in this.env);
|
|
510
|
+
debug.log('acp.claude', 'auth-source', {
|
|
511
|
+
apiKeyPresent: remaining.length > 0,
|
|
512
|
+
source: remaining.length > 0 ? 'billing-env' : 'subscription',
|
|
513
|
+
});
|
|
514
|
+
if (remaining.length > 0 && process.env.NODE_ENV !== 'test' && !process.env.ELANOUS_TEST_HOME) {
|
|
515
|
+
throw new Error('ACP Claude refused: billing authentication environment remains after scrub');
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const child = this.spawnChild(resolvedBin.path, this.spec.args, {
|
|
505
520
|
cwd: this.cwd,
|
|
506
521
|
env: this.env,
|
|
507
522
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
@@ -15,6 +15,8 @@ import { join, relative, resolve, isAbsolute, sep } from 'node:path';
|
|
|
15
15
|
import { startPty, mintPtyId, onPtyEvent, type PtyHandle, type StartOpts } from '../pty-shell/registry.js';
|
|
16
16
|
import { withChildPtyIdentity } from '../agent/pty-identity.js';
|
|
17
17
|
import { NESTED_AGENT_ENV_BLOCKLIST } from '../agent/nested-agent-env.js';
|
|
18
|
+
import { getNestDepth } from '../agent/nest-depth.js';
|
|
19
|
+
import { gateNestedElanousLaunch, nestedDepthCodexArgs, readNestedElanousDepth } from '../harness/nested-elanous-policy.js';
|
|
18
20
|
import { createWorktree, resolveMainRepoRoot, type CreateWorktreeOpts } from '../git-fs/worktree.js';
|
|
19
21
|
import { configuredWorktreeRoot } from '../user-config.js';
|
|
20
22
|
import { worktreeHasChanges, commitWorktree, changedFiles } from '../self-implement/seams.js';
|
|
@@ -321,7 +323,36 @@ export function resolveBackendSpawn(
|
|
|
321
323
|
// 지운 키 이름 — PTY 합성이 로그인 셸 캡처본에서 되살리지 않게 그대로 넘긴다(강제 env 로 다시 넣은 키는 뺀다).
|
|
322
324
|
// ⚠️ 과금 스크럽 키만 — 중첩 표지는 정체성 allowlist 가 다시 싣는 키가 있어 이 PR 의 범위 밖이다.
|
|
323
325
|
const unsetEnv = [...new Set(backend.scrubEnv ?? [])].filter((k) => !(k in env));
|
|
324
|
-
|
|
326
|
+
// Codex drops parent env under shell_environment_policy inherit=core, so the nested-depth
|
|
327
|
+
// marker rides on argv (`-c shell_environment_policy.set.ELANOUS_NESTED_DEPTH=<child>`).
|
|
328
|
+
// Backends that cannot carry it are refused only when a nested elanous launch was requested.
|
|
329
|
+
const args = [...backend.args, ...nestedDepthCodexArgs(backend.name, getNestDepth() + 1)];
|
|
330
|
+
return { cmd: backend.cmd, args, env, unsetEnv, nestedEnvRemovedCount, forcedEnv };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Refuse a requested nested elanous launch when this backend cannot carry the depth marker.
|
|
335
|
+
* Unset depth is 0. A normal mission spawn (`nestedLaunchRequested: false`) is never refused here.
|
|
336
|
+
*/
|
|
337
|
+
export function refuseNestedElanousOnBackend(input: {
|
|
338
|
+
readonly backendName: string;
|
|
339
|
+
readonly nestedLaunchRequested: boolean;
|
|
340
|
+
readonly allow?: boolean;
|
|
341
|
+
readonly configAllow?: boolean;
|
|
342
|
+
readonly env?: Record<string, string | undefined>;
|
|
343
|
+
}): { readonly refused: boolean; readonly reason?: string; readonly depth: number } {
|
|
344
|
+
const depth = readNestedElanousDepth(input.env);
|
|
345
|
+
const decision = gateNestedElanousLaunch({
|
|
346
|
+
backendName: input.backendName,
|
|
347
|
+
depth,
|
|
348
|
+
nestedLaunchRequested: input.nestedLaunchRequested,
|
|
349
|
+
...(input.allow === true ? { allow: true } : {}),
|
|
350
|
+
...(input.configAllow === true ? { configAllow: true } : {}),
|
|
351
|
+
});
|
|
352
|
+
if (input.nestedLaunchRequested && !decision.allowed) {
|
|
353
|
+
return { refused: true, reason: decision.reason, depth };
|
|
354
|
+
}
|
|
355
|
+
return { refused: false, depth };
|
|
325
356
|
}
|
|
326
357
|
|
|
327
358
|
/** Build the mission PTY options after identity is preallocated for child env propagation. */
|
|
@@ -491,6 +522,12 @@ export interface AgentMissionSpec {
|
|
|
491
522
|
memory?: boolean;
|
|
492
523
|
/** PTY 닉네임(휴먼 리더블·goto 로 나중 접근). 기본 branch. */
|
|
493
524
|
nickname?: string;
|
|
525
|
+
/**
|
|
526
|
+
* True only when this mission is itself a nested elanous launch.
|
|
527
|
+
* A backend that cannot carry the depth marker is then refused.
|
|
528
|
+
* Absent means an ordinary mission spawn — never refused for a missing marker.
|
|
529
|
+
*/
|
|
530
|
+
nestedElanousLaunch?: boolean;
|
|
494
531
|
/** Concurrent missions may share one decision stream alias without sharing a PTY or worktree. */
|
|
495
532
|
decisionMissionId?: string;
|
|
496
533
|
/** ⭐⭐ 그 브랜치를 이미 쥔 «소유» 워크트리가 있으면 지우지 말고 그대로 재사용하라고 요청한다
|
|
@@ -1534,6 +1571,19 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
|
|
|
1534
1571
|
// 구독 모드 — backend 지정 env 키 스크럽 후 강제 env까지 적용해 cmd·args·env를 순수 seam으로 구성한다.
|
|
1535
1572
|
// forcedEnv 이름의 후속 로깅은 이 착지의 의도적 경계이며, spawn에는 같은 seam의 env가 그대로 흐른다.
|
|
1536
1573
|
const spawnParams = resolveBackendSpawn(backend, process.env);
|
|
1574
|
+
const nestedRefusal = refuseNestedElanousOnBackend({
|
|
1575
|
+
backendName: backend.name,
|
|
1576
|
+
nestedLaunchRequested: spec.nestedElanousLaunch === true,
|
|
1577
|
+
env: process.env,
|
|
1578
|
+
});
|
|
1579
|
+
if (nestedRefusal.refused) {
|
|
1580
|
+
debug.log('agent-mission.nested', 'refused', { agent: backend.name, depth: nestedRefusal.depth, reason: nestedRefusal.reason });
|
|
1581
|
+
return {
|
|
1582
|
+
ok: false, worktree: spec.workdir ?? '', branch: spec.branch ?? null, rounds: 0,
|
|
1583
|
+
evidencePath: null, committed: false, usedOmniCrawl: false,
|
|
1584
|
+
detail: `nested elanous refused (${nestedRefusal.reason})`,
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1537
1587
|
const env = spawnParams.env;
|
|
1538
1588
|
if (backend.name === 'claude') {
|
|
1539
1589
|
const status = (deps.checkClaudeSubscription ?? checkClaudeSubscription)({ env });
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import { parseSince } from '../cli/logs-cli.js';
|
|
3
|
+
import {
|
|
4
|
+
SUPERVISOR_METRICS_DEFAULT_SINCE,
|
|
5
|
+
formatSupervisorMetrics,
|
|
6
|
+
loadSupervisorMetrics,
|
|
7
|
+
type ReadSupervisorLogs,
|
|
8
|
+
} from './supervisor-metrics.js';
|
|
9
|
+
|
|
10
|
+
export interface SupervisorMetricsCliDeps {
|
|
11
|
+
readonly read?: ReadSupervisorLogs;
|
|
12
|
+
readonly write?: (text: string) => void;
|
|
13
|
+
readonly nowMs?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function registerSupervisorMetricsCommand(parent: Command, deps: SupervisorMetricsCliDeps = {}): Command {
|
|
17
|
+
return parent.command('metrics')
|
|
18
|
+
.description('외부 에이전트 미션 슈퍼바이저 품질 지표 — 이미 남은 미션 기록만 읽는다')
|
|
19
|
+
.option('--since <window>', '조회 창 (30s/15m/2h/7d · 기본 7d)', SUPERVISOR_METRICS_DEFAULT_SINCE)
|
|
20
|
+
.option('--json', 'JSON 출력')
|
|
21
|
+
.action((opts: { since?: string; json?: boolean }) => {
|
|
22
|
+
const since = opts.since?.trim() || SUPERVISOR_METRICS_DEFAULT_SINCE;
|
|
23
|
+
const nowMs = deps.nowMs ?? Date.now();
|
|
24
|
+
const relative = /^(\d+)(s|m|h|d)$/.exec(since);
|
|
25
|
+
const unitMs = { s: 1_000, m: 60_000, h: 3_600_000, d: 86_400_000 } as const;
|
|
26
|
+
// parseSince returns an absolute epoch (including for relative inputs); anchor relative windows to this invocation's clock.
|
|
27
|
+
const sinceMs = relative
|
|
28
|
+
? nowMs - Number(relative[1]) * unitMs[relative[2] as keyof typeof unitMs]
|
|
29
|
+
: parseSince(since);
|
|
30
|
+
const write = deps.write ?? ((text: string) => { process.stdout.write(text); });
|
|
31
|
+
if (sinceMs === null || !Number.isFinite(sinceMs)) {
|
|
32
|
+
write(`--since 를 읽지 못함: ${since}\n`);
|
|
33
|
+
process.exitCode = 2;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const report = loadSupervisorMetrics(since, sinceMs, nowMs, deps.read);
|
|
37
|
+
if (opts.json) write(`${JSON.stringify(report)}\n`);
|
|
38
|
+
else write(`${formatSupervisorMetrics(report)}\n`);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// X2 — 외부 에이전트 미션 슈퍼바이저 품질 지표. 읽기 전용.
|
|
2
|
+
// 새 기록을 만들지 않는다. 이미 남는 미션 로그(logs.db)만 집계한다.
|
|
3
|
+
// 기록이 없는 지표는 0 이 아니라 «못 잼 · 이유»다. 창 안 미션이 0 이면 «미션 없음».
|
|
4
|
+
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import { LogStore, logsDbPath, type LogStoreRow } from '../mss/logging/log-store.js';
|
|
7
|
+
|
|
8
|
+
export const SUPERVISOR_METRICS_DEFAULT_SINCE = '7d';
|
|
9
|
+
|
|
10
|
+
const RESULT_CATEGORY = 'agent-mission';
|
|
11
|
+
const RESULT_EVENT = 'result';
|
|
12
|
+
const DECISION_CATEGORY = 'pty.decision';
|
|
13
|
+
const ANSWER_EVENT = 'answer';
|
|
14
|
+
const CONTROL_CATEGORY = 'autopilot.control';
|
|
15
|
+
const CONTROL_END_EVENT = 'end';
|
|
16
|
+
const STALL_CATEGORY = 'self-implement';
|
|
17
|
+
const STALL_EVENT = 'frame-stall';
|
|
18
|
+
const LADDER_CATEGORY = 'agent-mission.resources';
|
|
19
|
+
|
|
20
|
+
const LADDER_STEPS = ['needs', 'have', 'official-index', 'backend', 'discover'] as const;
|
|
21
|
+
|
|
22
|
+
export interface MetricRow {
|
|
23
|
+
readonly id: 'completion' | 'questionsAnswered' | 'unnecessaryInterventions' | 'stall' | 'ladderHits';
|
|
24
|
+
readonly label: string;
|
|
25
|
+
readonly status: 'measured' | 'unmeasured' | 'no-missions';
|
|
26
|
+
readonly reason?: string;
|
|
27
|
+
readonly value?: number | string;
|
|
28
|
+
readonly detail?: Record<string, number | string>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface SupervisorMetricsReport {
|
|
32
|
+
readonly since: string;
|
|
33
|
+
readonly sinceMs: number;
|
|
34
|
+
readonly missions: number;
|
|
35
|
+
readonly status: 'measured' | 'no-missions';
|
|
36
|
+
readonly metrics: readonly MetricRow[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface SupervisorLogRow {
|
|
40
|
+
readonly tsMs: number;
|
|
41
|
+
readonly category: string;
|
|
42
|
+
readonly event: string;
|
|
43
|
+
readonly data: Record<string, unknown> | null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ReadSupervisorLogs {
|
|
47
|
+
(sinceMs: number, nowMs: number): readonly SupervisorLogRow[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function parseData(raw: string | null): Record<string, unknown> | null {
|
|
51
|
+
if (!raw) return null;
|
|
52
|
+
try {
|
|
53
|
+
const parsed: unknown = JSON.parse(raw);
|
|
54
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed as Record<string, unknown> : null;
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function missionKey(data: Record<string, unknown> | null): string | null {
|
|
61
|
+
const runId = data?.runId;
|
|
62
|
+
if (typeof runId === 'string' && runId.trim()) return runId.trim();
|
|
63
|
+
const missionId = data?.missionId;
|
|
64
|
+
if (typeof missionId === 'string' && missionId.trim()) return missionId.trim();
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function linkedRows(rows: readonly SupervisorLogRow[], missionIds: ReadonlySet<string>): { linked: SupervisorLogRow[]; unlinked: boolean } {
|
|
69
|
+
const linked: SupervisorLogRow[] = [];
|
|
70
|
+
let unlinked = false;
|
|
71
|
+
for (const row of rows) {
|
|
72
|
+
const key = missionKey(row.data);
|
|
73
|
+
if (key && missionIds.has(key)) linked.push(row);
|
|
74
|
+
else unlinked = true;
|
|
75
|
+
}
|
|
76
|
+
return { linked, unlinked };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const UNLINKED_REASON = '못 잼 · 미션과 연결 불가';
|
|
80
|
+
|
|
81
|
+
function unmeasured(id: MetricRow['id'], label: string, reason: string): MetricRow {
|
|
82
|
+
return { id, label, status: 'unmeasured', reason };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** 주입된 로그만으로 다섯 지표를 집계한다. 스토어를 열지 않는다. */
|
|
86
|
+
export function aggregateSupervisorMetrics(
|
|
87
|
+
rows: readonly SupervisorLogRow[],
|
|
88
|
+
opts: { since?: string; sinceMs: number },
|
|
89
|
+
): SupervisorMetricsReport {
|
|
90
|
+
const since = opts.since ?? SUPERVISOR_METRICS_DEFAULT_SINCE;
|
|
91
|
+
const results = rows.filter((row) => row.category === RESULT_CATEGORY && row.event === RESULT_EVENT);
|
|
92
|
+
const missionIds = new Set(results.map((row) => missionKey(row.data)).filter((key): key is string => key !== null));
|
|
93
|
+
const missions = results.length;
|
|
94
|
+
if (missions === 0) {
|
|
95
|
+
const reason = '미션 없음';
|
|
96
|
+
return {
|
|
97
|
+
since,
|
|
98
|
+
sinceMs: opts.sinceMs,
|
|
99
|
+
missions: 0,
|
|
100
|
+
status: 'no-missions',
|
|
101
|
+
metrics: [
|
|
102
|
+
{ id: 'completion', label: '완주율', status: 'no-missions', reason },
|
|
103
|
+
{ id: 'questionsAnswered', label: '대화형 질문에 답해 넘긴 수', status: 'no-missions', reason },
|
|
104
|
+
{ id: 'unnecessaryInterventions', label: '불필요했던 슈퍼바이저 개입', status: 'no-missions', reason },
|
|
105
|
+
{ id: 'stall', label: '멈춤 감지 · 실제 멈춤', status: 'no-missions', reason },
|
|
106
|
+
{ id: 'ladderHits', label: '사다리 단계별 적중', status: 'no-missions', reason },
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const completed = results.filter((row) => row.data?.ok === true).length;
|
|
112
|
+
const completion: MetricRow = {
|
|
113
|
+
id: 'completion',
|
|
114
|
+
label: '완주율',
|
|
115
|
+
status: 'measured',
|
|
116
|
+
value: `${completed}/${missions}`,
|
|
117
|
+
detail: { completed, missions, rate: completed / missions },
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const answerCandidates = rows.filter((row) => row.category === DECISION_CATEGORY && row.event === ANSWER_EVENT);
|
|
121
|
+
const answers = linkedRows(answerCandidates, missionIds);
|
|
122
|
+
const questionsAnswered: MetricRow = answers.unlinked
|
|
123
|
+
? unmeasured('questionsAnswered', '대화형 질문에 답해 넘긴 수', UNLINKED_REASON)
|
|
124
|
+
: answers.linked.length === 0
|
|
125
|
+
? unmeasured('questionsAnswered', '대화형 질문에 답해 넘긴 수', '못 잼 · pty.decision answer 기록 없음')
|
|
126
|
+
: {
|
|
127
|
+
id: 'questionsAnswered',
|
|
128
|
+
label: '대화형 질문에 답해 넘긴 수',
|
|
129
|
+
status: 'measured',
|
|
130
|
+
value: answers.linked.length,
|
|
131
|
+
detail: { answered: answers.linked.length },
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const endCandidates = rows.filter((row) => row.category === CONTROL_CATEGORY && row.event === CONTROL_END_EVENT && row.data?.interventionLevel !== undefined);
|
|
135
|
+
const ends = linkedRows(endCandidates, missionIds);
|
|
136
|
+
// 종료 상태와 verdict 는 개입이 불필요했다는 인과 근거가 아니다.
|
|
137
|
+
const unnecessaryInterventions: MetricRow = unmeasured('unnecessaryInterventions', '불필요했던 슈퍼바이저 개입', ends.unlinked
|
|
138
|
+
? UNLINKED_REASON
|
|
139
|
+
: ends.linked.length === 0
|
|
140
|
+
? '못 잼 · autopilot.control end 개입 기록 없음'
|
|
141
|
+
: '못 잼 · 개입 불필요성 근거 기록 없음');
|
|
142
|
+
|
|
143
|
+
const stallCandidates = rows.filter((row) => row.category === STALL_CATEGORY && row.event === STALL_EVENT);
|
|
144
|
+
const stalls = linkedRows(stallCandidates, missionIds);
|
|
145
|
+
// frame-stall is a detector stage, not a record that a mission actually stopped.
|
|
146
|
+
const stall: MetricRow = unmeasured('stall', '멈춤 감지 · 실제 멈춤', stalls.unlinked
|
|
147
|
+
? UNLINKED_REASON
|
|
148
|
+
: stalls.linked.length === 0
|
|
149
|
+
? '못 잼 · self-implement frame-stall 기록 없음'
|
|
150
|
+
: '못 잼 · 실제 멈춤 근거 기록 없음');
|
|
151
|
+
|
|
152
|
+
const ladderCandidates = rows.filter((row) => row.category === LADDER_CATEGORY && LADDER_STEPS.includes(row.event as typeof LADDER_STEPS[number]));
|
|
153
|
+
const ladderRows = linkedRows(ladderCandidates, missionIds);
|
|
154
|
+
// VERIFY/ROUTE are decisions. No outcome record connects a step to an observed success.
|
|
155
|
+
const ladderHits: MetricRow = unmeasured('ladderHits', '사다리 단계별 적중', ladderRows.unlinked
|
|
156
|
+
? UNLINKED_REASON
|
|
157
|
+
: ladderRows.linked.length === 0
|
|
158
|
+
? '못 잼 · agent-mission.resources 사다리 기록 없음'
|
|
159
|
+
: '못 잼 · 사다리 적중 근거 기록 없음');
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
since,
|
|
163
|
+
sinceMs: opts.sinceMs,
|
|
164
|
+
missions,
|
|
165
|
+
status: 'measured',
|
|
166
|
+
metrics: [completion, questionsAnswered, unnecessaryInterventions, stall, ladderHits],
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function readSupervisorLogsFromStore(path: string = logsDbPath()): ReadSupervisorLogs {
|
|
171
|
+
return (sinceMs, nowMs) => {
|
|
172
|
+
if (!existsSync(path)) return [];
|
|
173
|
+
const store = LogStore.openReadOnly(path);
|
|
174
|
+
try {
|
|
175
|
+
const queried: LogStoreRow[] = store.queryAll({
|
|
176
|
+
exactCategories: [RESULT_CATEGORY, DECISION_CATEGORY, CONTROL_CATEGORY, STALL_CATEGORY, LADDER_CATEGORY],
|
|
177
|
+
sinceMs,
|
|
178
|
+
untilMs: nowMs,
|
|
179
|
+
});
|
|
180
|
+
return queried
|
|
181
|
+
.filter((row) => row.ts_ms >= sinceMs && row.ts_ms <= nowMs)
|
|
182
|
+
.filter((row) =>
|
|
183
|
+
(row.category === RESULT_CATEGORY && row.event === RESULT_EVENT)
|
|
184
|
+
|| (row.category === DECISION_CATEGORY && row.event === ANSWER_EVENT)
|
|
185
|
+
|| (row.category === CONTROL_CATEGORY && row.event === CONTROL_END_EVENT)
|
|
186
|
+
|| (row.category === STALL_CATEGORY && row.event === STALL_EVENT)
|
|
187
|
+
|| (row.category === LADDER_CATEGORY && (LADDER_STEPS as readonly string[]).includes(row.event)))
|
|
188
|
+
.map((row) => ({ tsMs: row.ts_ms, category: row.category, event: row.event, data: parseData(row.data) }));
|
|
189
|
+
} finally {
|
|
190
|
+
store.close();
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function loadSupervisorMetrics(
|
|
196
|
+
since: string,
|
|
197
|
+
sinceMs: number,
|
|
198
|
+
nowMs: number,
|
|
199
|
+
read: ReadSupervisorLogs = readSupervisorLogsFromStore(),
|
|
200
|
+
): SupervisorMetricsReport {
|
|
201
|
+
return aggregateSupervisorMetrics(read(sinceMs, nowMs), { since, sinceMs });
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function formatSupervisorMetrics(report: SupervisorMetricsReport): string {
|
|
205
|
+
const lines = [`agent-mission metrics · since ${report.since} · 미션 ${report.missions}`];
|
|
206
|
+
if (report.status === 'no-missions') {
|
|
207
|
+
lines.push('미션 없음');
|
|
208
|
+
return lines.join('\n');
|
|
209
|
+
}
|
|
210
|
+
for (const metric of report.metrics) {
|
|
211
|
+
if (metric.status === 'unmeasured') {
|
|
212
|
+
lines.push(`${metric.label}: ${metric.reason}`);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
lines.push(`${metric.label}: ${metric.value}`);
|
|
216
|
+
}
|
|
217
|
+
return lines.join('\n');
|
|
218
|
+
}
|
|
@@ -3,6 +3,7 @@ import { appendFileSync, mkdirSync, readFileSync, rmdirSync } from 'node:fs';
|
|
|
3
3
|
import { dirname, join, resolve } from 'node:path';
|
|
4
4
|
import { promisify } from 'node:util';
|
|
5
5
|
import { stateDirSourceForChild } from '../agent/identity-env.js';
|
|
6
|
+
import { oldDoorInternalEnv } from '../self-dev/old-door.js';
|
|
6
7
|
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
7
8
|
import type { TrackAgentDecision, TrackAgentInput, TrackAgentResult } from './track-agent.js';
|
|
8
9
|
|
|
@@ -128,7 +129,7 @@ export async function launchTrackAgentShadow(input: TrackAgentInput, options: Sh
|
|
|
128
129
|
const instanceRoot = effectiveInstanceRoot();
|
|
129
130
|
const { stdout } = await exec('bun', ['bin/elanous.mjs', '--config-dir', instanceRoot, ...values], {
|
|
130
131
|
cwd: process.env.ELANOUS_TOOL_CWD?.trim() || resolve(import.meta.dir, '../..'),
|
|
131
|
-
env: { ...process.env, ELANOUS_STATE_DIR: instanceRoot, ELANOUS_STATE_DIR_SOURCE: stateDirSourceForChild(instanceRoot) },
|
|
132
|
+
env: { ...process.env, ELANOUS_STATE_DIR: instanceRoot, ELANOUS_STATE_DIR_SOURCE: stateDirSourceForChild(instanceRoot), ...oldDoorInternalEnv('self-orchestrate') },
|
|
132
133
|
encoding: 'utf8', maxBuffer: 1024 * 1024,
|
|
133
134
|
});
|
|
134
135
|
return { stdout };
|