elanous 0.2.25 → 0.2.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/{1454-37fd16f479f5554a.js → 1454-a9c338f5bb556f42.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/{page-9360b1285b2a346e.js → page-a1ce306364f08da9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-07110da72c45e68f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-d2d76d4a7eae91bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-30007380f2e2a3c1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e5a9302c105725a3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-b93c04b5311c97ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-718a49e86bf25f9c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/{page-44eaad486bc98a45.js → page-20662900d6b4b455.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/control/{page-97fa6cf3d34c7eb9.js → page-f707a509515ac50c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/{page-305cd294e754906e.js → page-6a16a48d1aa6e91f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-fcb06e305f515527.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-6acf5ec4e6c6d1af.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-45889e7070d11126.js → page-39f82acf619cca23.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-16a2e893f83ae4d0.js → page-7c2c8f81b185f9c0.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-ae7aff07e32676bb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-56888bc744e08dac.js → page-482c60c1bc540d0a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-7cb996c1aa08b6f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/{page-952c6ceec6d9de38.js → page-623e08fe4477d606.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/{page-6d0202af568f4c63.js → page-db460cd8aecb8490.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-ff27902d0b11ef4f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-74366ae2693a7d95.js → page-8397a5c6ca569bf4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-4fb6fc34e20c07b7.js → page-1917ea87ea71ab6b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-e9502921f4f01b1f.js → page-25a302273b054fd7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-b6169ea2f09cf287.js → not-found-670510eda8b9443f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-d73c3f1e00d93986.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-65caacf54389081e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4bb7556e2e97a750.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-77e04b4f8762c666.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/{page-b9cc8a10bc6078b4.js → page-15c0a4095baddfba.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{page-3d3fc73dd187ed52.js → page-4eb96fc7241c2456.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-63e272a4cd3b6634.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-9c04dac0831c2bbb.js → page-0d64ff212dc55d03.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-7f096248993fc2d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-04de9ca6d52a3893.js → page-e47fc72049149a38.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-62527677bbc2fdc4.js → page-f7d808e31e005895.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-9a2e09c4a311764c.js → page-5c2b24b5496dad73.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-bfeb35d69e8b24bb.js → page-76cdb531668b5191.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-e2b63345bba1a11f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-b008aff96be56654.js → page-d376e0c83edc041a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-d6db55fe2c04d128.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-87b88f551f421d8b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/{page-6445ac4cbb8dd869.js → page-dab163b338e3b966.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-852170303c6cd755.js → page-08019efef111ed2e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-0260ffc7eda50db1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-97c7361cc2a19319.js → page-980ce6fe36ad95eb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-20162174b77f96cf.js → page-99c4e6c7345a9156.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-8c6901833f897496.js → page-0ab42e0f05823013.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ad9220422d20ba33.js +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +13 -13
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +13 -13
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +13 -13
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +13 -13
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +13 -13
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +13 -13
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +13 -13
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +13 -13
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +13 -13
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +13 -13
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +13 -13
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +13 -13
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +13 -13
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +13 -13
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +13 -13
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +13 -13
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +13 -13
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +13 -13
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +13 -13
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +13 -13
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +13 -13
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +13 -13
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +13 -13
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +13 -13
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +13 -13
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +13 -13
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +13 -13
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +13 -13
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +14 -14
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +13 -13
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +13 -13
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +13 -13
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +13 -13
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +13 -13
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +13 -13
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +13 -13
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +13 -13
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +13 -13
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +13 -13
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +13 -13
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +13 -13
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +13 -13
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +13 -13
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +13 -13
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +13 -13
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +13 -13
- package/package.json +1 -1
- package/scripts/ci-typecheck-changed.ts +81 -2
- package/scripts/coord-msg-bridge.ts +209 -0
- package/scripts/coord-post.sh +40 -7
- package/scripts/coord-requests-bridge.ts +10 -5
- package/scripts/expose-rubric-suggest.ts +1 -1
- package/scripts/measure-tsc-affected-roots.ts +10 -1
- package/scripts/release-loop/export-check-node.ts +8 -5
- package/scripts/release-loop/node-verdict.ts +7 -2
- package/src/agent/task-notification.ts +30 -1
- package/src/agent-substrate/review-observation.ts +9 -2
- package/src/agent-substrate/self-review-cli.ts +28 -6
- package/src/boot/daemon-tools/self-implement.ts +72 -4
- package/src/budget/llm-cost.ts +5 -1
- package/src/cli/control-cli.ts +29 -0
- package/src/cli/doctor-cli.ts +90 -8
- package/src/cli/msg-cli.ts +15 -5
- package/src/cli/pr-cli.ts +53 -0
- package/src/cli/pr-land-overlap-evidence.ts +18 -10
- package/src/cli/release-cli.ts +32 -5
- package/src/cli/seat-cli.ts +121 -0
- package/src/cli/self-utterances-cli.ts +140 -0
- package/src/cli/tasks-cli.ts +139 -9
- package/src/context-card/cell.ts +40 -0
- package/src/dashboard/boot-timing.ts +35 -0
- package/src/dashboard/index.ts +86 -24
- package/src/dashboard/input/chat-main-plain-turn-runtime.ts +26 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +6 -0
- package/src/debug/log.ts +1 -1
- package/src/domains/release-tool.ts +29 -12
- package/src/domains/surface-events.ts +16 -0
- package/src/elanous-config-dir.ts +2 -2
- package/src/fleet/host-doctor.ts +73 -2
- package/src/graph-runner/runner.ts +3 -1
- package/src/harness/harness-cli-command.ts +46 -4
- package/src/harness/harness-stop.ts +11 -7
- package/src/index.ts +35 -4
- package/src/instance/current.ts +2 -6
- package/src/instance/resolve.ts +5 -6
- package/src/llm/openai-sampling-fields.ts +89 -0
- package/src/llm/runtime-model-family.ts +61 -0
- package/src/llm/zai-coding-plan.ts +27 -1
- package/src/llm.ts +13 -89
- package/src/machines/machine-ledger.ts +31 -3
- package/src/maturity/cli-maturity.ts +13 -3
- package/src/msg/msg-store.ts +69 -7
- package/src/ops/overview-collect.ts +3 -2
- package/src/ops/overview-owner.ts +29 -0
- package/src/plugin-hooks/shell-hook.ts +17 -2
- package/src/plugin-missions/evaluator-runner.ts +18 -2
- package/src/plugins/install/plugin-install.ts +11 -1
- package/src/prompt-enhance/enhance.ts +11 -0
- package/src/release-loop/checklist.ts +41 -4
- package/src/release-loop/feature-store.ts +10 -9
- package/src/seat-loop/seat-loop.ts +5 -1
- package/src/self-dev/draft-sweep.ts +23 -14
- package/src/self-dev/orchestrate.ts +6 -2
- package/src/self-dev/run-supervisor.ts +35 -1
- package/src/self-implement/goal-author.ts +3 -27
- package/src/self-implement/goal-command-execution.ts +26 -0
- package/src/self-implement/orchestrator.ts +2 -1
- package/src/self-implement/pwa-import-graph.ts +194 -7
- package/src/self-implement/self-implement-runtime.ts +126 -21
- package/src/session/index.ts +14 -4
- package/src/session-runtime/index.ts +55 -11
- package/src/task-agent/cover.ts +28 -9
- package/src/task-agent/live-moves.ts +14 -6
- package/src/task-agent/rejudge-on-head.ts +220 -23
- package/src/task-agent/review-result-capture.ts +64 -1
- package/src/task-agent/shadow.ts +137 -22
- package/src/task-agent/task-hand.ts +18 -2
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +1 -0
- package/src/tool-runtime/tool-search-runtime.ts +25 -0
- package/src/tui.ts +20 -1
- package/src/user-config.ts +3 -59
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f08f6529d09ffae1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-f000d54d258f9ef6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-b12149c51f7d04b1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-22f674e7e4c2c9d5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-8888733e40cea6d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-9717ec6964b92276.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-dea4b795741126fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-35e05d7450461a67.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-b2638013c0bf7dfd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-c44f93a41167536f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-1b810474507f3702.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-6a14333d1b1b3967.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-8c91a9a66c9ec90a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4e634c5b98fd4e93.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ed3070d637850548.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-128458c8d86fbb5f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-cf0c2b0183991d09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-89cb33bced824e4a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-814394dcbae789b1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-6a87325c87f09dd2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-087ceeb975ef272b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-05a4b2c9b91034aa.js +0 -1
- /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → Ev2jivDMTSoEc-5rIXdcy}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{dRS6lEhSRRBr1abbzp7fJ → Ev2jivDMTSoEc-5rIXdcy}/_ssgManifest.js +0 -0
|
@@ -10,6 +10,16 @@ export interface MachineRow {
|
|
|
10
10
|
locationStatus: string;
|
|
11
11
|
role: string;
|
|
12
12
|
duties: string;
|
|
13
|
+
/** Fleet intent only. Live membership comes from Nexus machine resources. */
|
|
14
|
+
fleet?: MachineFleetIntent;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface MachineFleetIntent {
|
|
18
|
+
sshName: string;
|
|
19
|
+
role: 'worker' | 'hq';
|
|
20
|
+
platform: 'darwin' | 'linux' | 'wsl';
|
|
21
|
+
podPool: string;
|
|
22
|
+
cap: number;
|
|
13
23
|
}
|
|
14
24
|
|
|
15
25
|
export interface DeviceRow {
|
|
@@ -59,18 +69,36 @@ export function validateMachineLedger(value: unknown): MachineLedger {
|
|
|
59
69
|
keys(data, ['title', 'policy', 'machines', 'devices', 'rules', 'changes'], 'ledger');
|
|
60
70
|
if (!Array.isArray(data.machines) || !Array.isArray(data.devices)) throw new Error('invalid machines or devices');
|
|
61
71
|
const ids = new Set<string>();
|
|
62
|
-
const rows = <T extends { id: string }>(entries: unknown[], fields: string[], label: string): T[] => entries.map((entry, index) => {
|
|
72
|
+
const rows = <T extends { id: string }>(entries: unknown[], fields: string[], label: string, extraFields: string[] = []): T[] => entries.map((entry, index) => {
|
|
63
73
|
const row = record(entry, `${label}[${index}]`);
|
|
64
|
-
keys(row, fields, label);
|
|
74
|
+
keys(row, [...fields, ...extraFields], label);
|
|
65
75
|
const result = Object.fromEntries(fields.map((field) => [field, text(row[field], `${label}.${field}`)])) as T;
|
|
66
76
|
if (!idPattern.test(result.id) || ids.has(result.id)) throw new Error(`empty, duplicate or invalid id: ${result.id}`);
|
|
67
77
|
ids.add(result.id);
|
|
68
78
|
return result;
|
|
69
79
|
});
|
|
80
|
+
const machines = rows<MachineRow>(data.machines, ['id', 'name', 'specs', 'locationStatus', 'role', 'duties'], 'machines', ['fleet']);
|
|
81
|
+
const sshNames = new Set<string>();
|
|
82
|
+
for (let index = 0; index < machines.length; index++) {
|
|
83
|
+
const raw = record(data.machines[index], `machines[${index}]`);
|
|
84
|
+
if (raw.fleet === undefined) continue;
|
|
85
|
+
const fleet = record(raw.fleet, `machines[${index}].fleet`);
|
|
86
|
+
keys(fleet, ['sshName', 'role', 'platform', 'podPool', 'cap'], 'fleet');
|
|
87
|
+
const sshName = text(fleet.sshName, 'fleet.sshName');
|
|
88
|
+
const role = text(fleet.role, 'fleet.role');
|
|
89
|
+
const platform = text(fleet.platform, 'fleet.platform');
|
|
90
|
+
const podPool = text(fleet.podPool, 'fleet.podPool');
|
|
91
|
+
if (!idPattern.test(sshName) || sshNames.has(sshName)) throw new Error(`duplicate or invalid fleet.sshName: ${sshName}`);
|
|
92
|
+
if (role !== 'worker' && role !== 'hq') throw new Error(`invalid fleet.role: ${role}`);
|
|
93
|
+
if (platform !== 'darwin' && platform !== 'linux' && platform !== 'wsl') throw new Error(`invalid fleet.platform: ${platform}`);
|
|
94
|
+
if (!Number.isSafeInteger(fleet.cap) || (fleet.cap as number) < 0) throw new Error('invalid fleet.cap');
|
|
95
|
+
sshNames.add(sshName);
|
|
96
|
+
machines[index]!.fleet = { sshName, role, platform, podPool, cap: fleet.cap as number };
|
|
97
|
+
}
|
|
70
98
|
return {
|
|
71
99
|
title: text(data.title, 'title'),
|
|
72
100
|
policy: list(data.policy, 'policy'),
|
|
73
|
-
machines
|
|
101
|
+
machines,
|
|
74
102
|
devices: rows<DeviceRow>(data.devices, ['id', 'name', 'status', 'purpose'], 'devices'),
|
|
75
103
|
rules: list(data.rules, 'rules'),
|
|
76
104
|
changes: list(data.changes, 'changes'),
|
|
@@ -18,14 +18,24 @@ export function cliRootVisibleFor(name: string, role: Role, { showAll }: { showA
|
|
|
18
18
|
return grade === 'stable' || (role === 'contributor' && (grade === 'beta' || grade === 'tool'));
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
/**
|
|
22
|
-
export
|
|
21
|
+
/** Root --help first screen (SETUP-HELP-SLIM): the three stable onboarding commands. */
|
|
22
|
+
export const FIRST_SCREEN_COMMANDS: readonly string[] = ['start', 'setup', 'ask'];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Only help is filtered: Commander still dispatches explicitly typed commands.
|
|
26
|
+
* firstScreen narrows the visible set to FIRST_SCREEN_COMMANDS and showAll wins over it;
|
|
27
|
+
* the caller opts in per role — the wired default (deferred slice) enables it for the
|
|
28
|
+
* general role only, so owner/contributor keep today's list.
|
|
29
|
+
*/
|
|
30
|
+
export function filterCliRootHelp(commands: readonly Command[], role: Role, showAll: boolean, firstScreen: boolean = false): { shown: number; hidden: number } {
|
|
31
|
+
const firstScreenActive = firstScreen && !showAll;
|
|
23
32
|
let shown = 0;
|
|
24
33
|
let hidden = 0;
|
|
25
34
|
for (const command of commands) {
|
|
26
35
|
const grade = cliRootMaturity(command.name(), commands);
|
|
27
36
|
if (!grade) throw new Error(`cliRoot 에 등급 없는 명령: ${command.name()}`);
|
|
28
|
-
const visible = cliRootVisibleFor(command.name(), role, { showAll }, commands)
|
|
37
|
+
const visible = cliRootVisibleFor(command.name(), role, { showAll }, commands)
|
|
38
|
+
&& (!firstScreenActive || FIRST_SCREEN_COMMANDS.includes(command.name()));
|
|
29
39
|
(command as Command & { _hidden: boolean })._hidden = !visible;
|
|
30
40
|
if (visible) shown++;
|
|
31
41
|
else hidden++;
|
package/src/msg/msg-store.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Database } from 'bun:sqlite';
|
|
2
|
-
import { mkdirSync } from 'node:fs';
|
|
2
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { elanousStateRoot } from '../autopilot/state-paths.js';
|
|
5
5
|
|
|
@@ -20,7 +20,7 @@ export interface UnreadRecipient {
|
|
|
20
20
|
count: number;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
const SEAT_ALIASES: Readonly<Record<string, string>> = {
|
|
23
|
+
export const SEAT_ALIASES: Readonly<Record<string, string>> = {
|
|
24
24
|
S: 'OP', OP: 'OP', COO: 'OP',
|
|
25
25
|
T: 'MK', MK: 'MK', CMO: 'MK',
|
|
26
26
|
O: 'TC', TC: 'TC', CTO: 'TC',
|
|
@@ -41,6 +41,7 @@ export function validateMessageEnvelope(input: unknown): MessageEnvelope {
|
|
|
41
41
|
const record = input as Record<string, unknown>;
|
|
42
42
|
const from = canonicalSeatId(record.from as string);
|
|
43
43
|
const to = canonicalSeatId(record.to as string);
|
|
44
|
+
if (from === 'ALL') throw new Error('ALL cannot send messages');
|
|
44
45
|
if (typeof record.body !== 'string' || !record.body.trim()) throw new Error('message body must be nonempty');
|
|
45
46
|
if (record.kind !== undefined && (typeof record.kind !== 'string' || !/^[a-z][a-z0-9_-]{0,63}$/.test(record.kind))) {
|
|
46
47
|
throw new Error('invalid message kind');
|
|
@@ -54,6 +55,13 @@ function validCursor(cursor: number): number {
|
|
|
54
55
|
return cursor;
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
function unreadCountOn(db: Database, to: string): number {
|
|
59
|
+
return (db.query(`SELECT COUNT(*) AS count FROM msg_messages m
|
|
60
|
+
WHERE m.id > COALESCE((SELECT last_id FROM msg_cursors WHERE recipient = ?), 0)
|
|
61
|
+
AND (m.recipient = ? OR (m.recipient = 'ALL' AND m.sender <> ?))`)
|
|
62
|
+
.get(to, to, to) as { count: number }).count;
|
|
63
|
+
}
|
|
64
|
+
|
|
57
65
|
interface MessageRow {
|
|
58
66
|
id: number;
|
|
59
67
|
sender: string;
|
|
@@ -88,10 +96,37 @@ export class MsgStore {
|
|
|
88
96
|
CREATE TABLE IF NOT EXISTS msg_cursors (
|
|
89
97
|
recipient TEXT PRIMARY KEY,
|
|
90
98
|
last_id INTEGER NOT NULL DEFAULT 0 CHECK(last_id >= 0)
|
|
99
|
+
);
|
|
100
|
+
CREATE TABLE IF NOT EXISTS msg_provenance (
|
|
101
|
+
provenance_key TEXT PRIMARY KEY,
|
|
102
|
+
message_id INTEGER NOT NULL REFERENCES msg_messages(id)
|
|
91
103
|
);`);
|
|
92
104
|
}
|
|
93
105
|
|
|
94
106
|
append(input: MessageEnvelope): Message {
|
|
107
|
+
return this.insertMessage(input);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Append once for a provenance key such as coord:<commentId>:<recipient>. */
|
|
111
|
+
appendWithProvenance(input: MessageEnvelope, provenanceKey: string): Message {
|
|
112
|
+
if (typeof provenanceKey !== 'string' || !/^coord:[^:]+:[^:]+$/.test(provenanceKey)) {
|
|
113
|
+
throw new Error('invalid message provenance key');
|
|
114
|
+
}
|
|
115
|
+
const msg = validateMessageEnvelope(input);
|
|
116
|
+
if (provenanceKey.split(':')[2] !== msg.to) throw new Error('provenance recipient does not match message recipient');
|
|
117
|
+
return this.db.transaction(() => {
|
|
118
|
+
const existing = this.db.query(`SELECT m.id, m.sender, m.recipient, m.body, m.kind, m.created_at
|
|
119
|
+
FROM msg_provenance p JOIN msg_messages m ON m.id = p.message_id WHERE p.provenance_key = ?`)
|
|
120
|
+
.get(provenanceKey) as MessageRow | null;
|
|
121
|
+
if (existing) return fromRow(existing);
|
|
122
|
+
const message = this.insertMessage(msg);
|
|
123
|
+
this.db.query('INSERT INTO msg_provenance(provenance_key, message_id) VALUES (?, ?)')
|
|
124
|
+
.run(provenanceKey, message.id);
|
|
125
|
+
return message;
|
|
126
|
+
}).immediate();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private insertMessage(input: MessageEnvelope): Message {
|
|
95
130
|
const msg = validateMessageEnvelope(input);
|
|
96
131
|
const createdAt = new Date().toISOString();
|
|
97
132
|
const result = this.db.query(`INSERT INTO msg_messages(sender, recipient, body, kind, created_at)
|
|
@@ -104,10 +139,12 @@ export class MsgStore {
|
|
|
104
139
|
/** Messages strictly after the supplied cursor, ordered by durable insertion ID. */
|
|
105
140
|
listByRecipient(recipient: string, after: number = 0, limit: number = 100): Message[] {
|
|
106
141
|
const to = canonicalSeatId(recipient);
|
|
142
|
+
if (to === 'ALL') throw new Error('ALL is a topic, not a recipient');
|
|
107
143
|
validCursor(after);
|
|
108
144
|
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1000) throw new Error('limit must be between 1 and 1000');
|
|
109
145
|
return (this.db.query(`SELECT id, sender, recipient, body, kind, created_at FROM msg_messages
|
|
110
|
-
WHERE
|
|
146
|
+
WHERE id > ? AND (recipient = ? OR (recipient = 'ALL' AND sender <> ?))
|
|
147
|
+
ORDER BY id ASC LIMIT ?`).all(after, to, to, limit) as MessageRow[]).map(fromRow);
|
|
111
148
|
}
|
|
112
149
|
|
|
113
150
|
list(recipient: string, after: number = 0, limit: number = 100): Message[] {
|
|
@@ -116,12 +153,14 @@ export class MsgStore {
|
|
|
116
153
|
|
|
117
154
|
getCursor(recipient: string): number {
|
|
118
155
|
const to = canonicalSeatId(recipient);
|
|
156
|
+
if (to === 'ALL') throw new Error('ALL is a topic, not a recipient');
|
|
119
157
|
return (this.db.query('SELECT last_id FROM msg_cursors WHERE recipient = ?').get(to) as { last_id: number } | null)?.last_id ?? 0;
|
|
120
158
|
}
|
|
121
159
|
|
|
122
160
|
/** Atomically reject future IDs and keep concurrent or stale acknowledgements from rewinding. */
|
|
123
161
|
advanceCursor(recipient: string, cursor: number): number {
|
|
124
162
|
const to = canonicalSeatId(recipient);
|
|
163
|
+
if (to === 'ALL') throw new Error('ALL is a topic, not a recipient');
|
|
125
164
|
validCursor(cursor);
|
|
126
165
|
const result = this.db.query(`INSERT INTO msg_cursors(recipient, last_id)
|
|
127
166
|
SELECT ?, ? WHERE ? <= (SELECT COALESCE(MAX(id), 0) FROM msg_messages)
|
|
@@ -133,11 +172,24 @@ export class MsgStore {
|
|
|
133
172
|
|
|
134
173
|
ack(recipient: string, cursor: number): number { return this.advanceCursor(recipient, cursor); }
|
|
135
174
|
|
|
175
|
+
unreadCount(recipient: string): number {
|
|
176
|
+
const to = canonicalSeatId(recipient);
|
|
177
|
+
if (to === 'ALL') throw new Error('ALL is a topic, not a recipient');
|
|
178
|
+
return unreadCountOn(this.db, to);
|
|
179
|
+
}
|
|
180
|
+
|
|
136
181
|
unreadRecipients(): UnreadRecipient[] {
|
|
137
|
-
return this.db.query(`
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
182
|
+
return this.db.query(`WITH recipients(recipient) AS (
|
|
183
|
+
SELECT 'OP' UNION SELECT 'MK' UNION SELECT 'TC' UNION SELECT 'UX'
|
|
184
|
+
UNION SELECT recipient FROM msg_messages WHERE recipient <> 'ALL'
|
|
185
|
+
UNION SELECT recipient FROM msg_cursors WHERE recipient <> 'ALL'
|
|
186
|
+
)
|
|
187
|
+
SELECT r.recipient AS recipient, COUNT(m.id) AS count
|
|
188
|
+
FROM recipients r
|
|
189
|
+
LEFT JOIN msg_cursors c ON c.recipient = r.recipient
|
|
190
|
+
JOIN msg_messages m ON m.id > COALESCE(c.last_id, 0)
|
|
191
|
+
AND (m.recipient = r.recipient OR (m.recipient = 'ALL' AND m.sender <> r.recipient))
|
|
192
|
+
GROUP BY r.recipient ORDER BY r.recipient ASC`).all() as UnreadRecipient[];
|
|
141
193
|
}
|
|
142
194
|
|
|
143
195
|
unread(): UnreadRecipient[] { return this.unreadRecipients(); }
|
|
@@ -147,3 +199,13 @@ export class MsgStore {
|
|
|
147
199
|
|
|
148
200
|
export function defaultMsgStorePath(): string { return join(elanousStateRoot(), 'msg', 'messages.db'); }
|
|
149
201
|
export function openMsgStore(path?: string): MsgStore { return new MsgStore(path); }
|
|
202
|
+
|
|
203
|
+
/** Observe one recipient without creating a store or advancing its acknowledgement. */
|
|
204
|
+
export function readUnreadCount(recipient: string, path: string = defaultMsgStorePath()): number | null {
|
|
205
|
+
const to = canonicalSeatId(recipient);
|
|
206
|
+
if (to === 'ALL') throw new Error('ALL is a topic, not a recipient');
|
|
207
|
+
if (!existsSync(path)) return null;
|
|
208
|
+
const db = new Database(path, { readonly: true });
|
|
209
|
+
try { return unreadCountOn(db, to); }
|
|
210
|
+
finally { db.close(); }
|
|
211
|
+
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
type OverviewSnapshot, type OwnerTaskRow, type ParentsInfo, type PodMemberRow, type ReleaseRunInfo, type ScheduleInfo,
|
|
17
17
|
} from './overview.js';
|
|
18
18
|
import { SEAT_ERA_SOURCE, readDispatcherBeats, readDispatcherCapacity, readSeatEraHanded, seatEraFeederDir } from './overview-seat-era.js';
|
|
19
|
+
import { matchesOverviewOwner, normalizeOverviewOwner } from './overview-owner.js';
|
|
19
20
|
|
|
20
21
|
const GB = 1024 ** 3;
|
|
21
22
|
/** 하트비트 주기 30 s × 2 (RFC §A2 «낡음»). */
|
|
@@ -77,8 +78,8 @@ export function ownerRows(cards: readonly TaskCard[], sinceIso: string, ownerFil
|
|
|
77
78
|
const rows: Record<string, OwnerTaskRow> = {};
|
|
78
79
|
for (const card of cards) {
|
|
79
80
|
if (!card || typeof card.createdAt !== 'string' || card.createdAt < sinceIso) continue;
|
|
80
|
-
const owner = card.seat
|
|
81
|
-
if (ownerFilter
|
|
81
|
+
const owner = normalizeOverviewOwner(card.seat);
|
|
82
|
+
if (ownerFilter !== undefined && !matchesOverviewOwner(owner, ownerFilter)) continue;
|
|
82
83
|
const row = rows[owner] ??= { handed: 0, launched: 0, prBound: 0, failed: 0, launchFailed: 0 };
|
|
83
84
|
if (card.status === 'handed') row.handed++;
|
|
84
85
|
else if (card.status === 'launched') row.launched++;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Owner labels used by ops overview task rows and the --owner filter. */
|
|
2
|
+
type OverviewOwnerKind = 'seat' | 'steward' | 'orchestrator' | 'external';
|
|
3
|
+
type OverviewOwner = `${OverviewOwnerKind}:${string}` | 'unassigned';
|
|
4
|
+
|
|
5
|
+
const OWNER_KINDS = new Set<OverviewOwnerKind>(['seat', 'steward', 'orchestrator', 'external']);
|
|
6
|
+
|
|
7
|
+
/** Normalize a task-card owner into a stable kind-prefixed overview key. */
|
|
8
|
+
export function normalizeOverviewOwner(value: unknown): OverviewOwner {
|
|
9
|
+
if (typeof value !== 'string' || value.trim() === '') return 'unassigned';
|
|
10
|
+
const normalized = value.trim();
|
|
11
|
+
const separator = normalized.indexOf(':');
|
|
12
|
+
if (separator >= 0) {
|
|
13
|
+
const kind = normalized.slice(0, separator).toLowerCase() as OverviewOwnerKind;
|
|
14
|
+
const name = normalized.slice(separator + 1).trim();
|
|
15
|
+
if (OWNER_KINDS.has(kind) && name) return `${kind}:${name}`;
|
|
16
|
+
}
|
|
17
|
+
const kind = normalized.toLowerCase();
|
|
18
|
+
if (kind === 'steward' || kind === 'orchestrator' || kind === 'external') return `${kind}:default`;
|
|
19
|
+
return `seat:${normalized}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Whether a normalized owner key matches a CLI filter, accepting either the full key or its kind. */
|
|
23
|
+
export function matchesOverviewOwner(owner: OverviewOwner, filter?: string): boolean {
|
|
24
|
+
if (!filter || filter === 'all') return true;
|
|
25
|
+
const normalizedFilter = filter.trim();
|
|
26
|
+
if (!normalizedFilter) return true;
|
|
27
|
+
if (normalizedFilter.toLowerCase() === 'unassigned') return owner === 'unassigned';
|
|
28
|
+
return owner === normalizeOverviewOwner(normalizedFilter) || owner.startsWith(`${normalizedFilter.toLowerCase()}:`);
|
|
29
|
+
}
|
|
@@ -118,6 +118,7 @@ function runShell(opts: RunShellOpts): Promise<RunShellResult> {
|
|
|
118
118
|
let stdout = '';
|
|
119
119
|
let stderrBuffer = '';
|
|
120
120
|
let killed = false;
|
|
121
|
+
let stdinError: string | undefined;
|
|
121
122
|
const onAbort = () => {
|
|
122
123
|
if (!killed) {
|
|
123
124
|
killed = true;
|
|
@@ -132,6 +133,14 @@ function runShell(opts: RunShellOpts): Promise<RunShellResult> {
|
|
|
132
133
|
opts.abortSignal.addEventListener('abort', onAbort, { once: true });
|
|
133
134
|
|
|
134
135
|
child.stdout?.on('data', (chunk) => { stdout += String(chunk); });
|
|
136
|
+
// A short-lived command can close its read end before end() finishes.
|
|
137
|
+
// EPIPE says only that stdin was unread; stdout and exit status still
|
|
138
|
+
// determine the hook result. Other pipe errors are real I/O failures.
|
|
139
|
+
child.stdin?.on('error', (err: NodeJS.ErrnoException) => {
|
|
140
|
+
if (err.code === 'EPIPE') return;
|
|
141
|
+
stdinError = `stdin error: ${err.message}`;
|
|
142
|
+
opts.onStderr(stdinError);
|
|
143
|
+
});
|
|
135
144
|
child.stderr?.on('data', (chunk) => {
|
|
136
145
|
stderrBuffer += String(chunk);
|
|
137
146
|
let idx;
|
|
@@ -145,13 +154,19 @@ function runShell(opts: RunShellOpts): Promise<RunShellResult> {
|
|
|
145
154
|
clearTimeout(timer);
|
|
146
155
|
if (stderrBuffer.trim()) opts.onStderr(stderrBuffer.trim());
|
|
147
156
|
// killed => treat as a non-zero exit so dispatcher abort.
|
|
148
|
-
resolve({ stdout, exitCode: killed ? 124 : (code ?? 1) });
|
|
157
|
+
resolve({ stdout, exitCode: killed ? 124 : stdinError ? 127 : (code ?? 1), error: stdinError });
|
|
149
158
|
});
|
|
150
159
|
child.on('error', (err) => {
|
|
151
160
|
clearTimeout(timer);
|
|
152
161
|
opts.onStderr(`spawn error: ${err.message}`);
|
|
153
162
|
resolve({ stdout, exitCode: 127 });
|
|
154
163
|
});
|
|
155
|
-
try { child.stdin?.end(opts.stdin); }
|
|
164
|
+
try { child.stdin?.end(opts.stdin); }
|
|
165
|
+
catch (err) {
|
|
166
|
+
if ((err as NodeJS.ErrnoException).code !== 'EPIPE') {
|
|
167
|
+
stdinError = `stdin error: ${(err as Error).message}`;
|
|
168
|
+
opts.onStderr(stdinError);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
156
171
|
});
|
|
157
172
|
}
|
|
@@ -137,6 +137,7 @@ function runShell(opts: RunShellOpts): Promise<RunShellResult> {
|
|
|
137
137
|
let stderrBuf = '';
|
|
138
138
|
let killed = false;
|
|
139
139
|
let timedOut = false;
|
|
140
|
+
let stdinError: string | undefined;
|
|
140
141
|
const kill = (timeout: boolean) => {
|
|
141
142
|
if (killed) return;
|
|
142
143
|
killed = true;
|
|
@@ -150,6 +151,14 @@ function runShell(opts: RunShellOpts): Promise<RunShellResult> {
|
|
|
150
151
|
opts.abortSignal.addEventListener('abort', () => kill(false), { once: true });
|
|
151
152
|
|
|
152
153
|
child.stdout?.on('data', (chunk) => { stdout += String(chunk); });
|
|
154
|
+
// Early exit may close stdin before end() finishes. A broken pipe must
|
|
155
|
+
// not replace the child's stdout/exit result; other write errors must
|
|
156
|
+
// surface as evaluator errors.
|
|
157
|
+
child.stdin?.on('error', (err: NodeJS.ErrnoException) => {
|
|
158
|
+
if (err.code === 'EPIPE') return;
|
|
159
|
+
stdinError = `stdin error: ${err.message}`;
|
|
160
|
+
opts.onStderr(stdinError);
|
|
161
|
+
});
|
|
153
162
|
child.stderr?.on('data', (chunk) => {
|
|
154
163
|
stderrBuf += String(chunk);
|
|
155
164
|
let idx;
|
|
@@ -164,8 +173,9 @@ function runShell(opts: RunShellOpts): Promise<RunShellResult> {
|
|
|
164
173
|
if (stderrBuf.trim()) opts.onStderr(stderrBuf.trim());
|
|
165
174
|
resolve({
|
|
166
175
|
stdout,
|
|
167
|
-
exitCode: timedOut ? 124 : (code ?? 1),
|
|
176
|
+
exitCode: timedOut ? 124 : stdinError ? 127 : (code ?? 1),
|
|
168
177
|
timedOut,
|
|
178
|
+
error: stdinError,
|
|
169
179
|
});
|
|
170
180
|
});
|
|
171
181
|
child.on('error', (err) => {
|
|
@@ -173,6 +183,12 @@ function runShell(opts: RunShellOpts): Promise<RunShellResult> {
|
|
|
173
183
|
opts.onStderr(`spawn error: ${err.message}`);
|
|
174
184
|
resolve({ stdout, exitCode: 127, timedOut: false });
|
|
175
185
|
});
|
|
176
|
-
try { child.stdin?.end(opts.stdin); }
|
|
186
|
+
try { child.stdin?.end(opts.stdin); }
|
|
187
|
+
catch (err) {
|
|
188
|
+
if ((err as NodeJS.ErrnoException).code !== 'EPIPE') {
|
|
189
|
+
stdinError = `stdin error: ${(err as Error).message}`;
|
|
190
|
+
opts.onStderr(stdinError);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
177
193
|
});
|
|
178
194
|
}
|
|
@@ -438,6 +438,13 @@ function declaresMain(packageRoot: string): boolean {
|
|
|
438
438
|
return !existsSync(join(packageRoot, 'plugin.json')) && !existsSync(join(packageRoot, '.codex-plugin', 'plugin.json'));
|
|
439
439
|
}
|
|
440
440
|
|
|
441
|
+
/** [wiring] Caller: installPlugin. The PluginManifest normalized by
|
|
442
|
+
* loadPluginManifestFromDir is handed into the install path here — the install
|
|
443
|
+
* destination follows the manifest identity, never the source spec/dir name. */
|
|
444
|
+
export function pluginInstallPath(root: string, market: string, manifest: PluginManifest): string {
|
|
445
|
+
return join(root, 'plugins', market, manifest.id, manifest.version);
|
|
446
|
+
}
|
|
447
|
+
|
|
441
448
|
export async function installPlugin(spec: string, opts: InstallOptions = {}): Promise<InstalledPlugin> {
|
|
442
449
|
const root = resolve(opts.root ?? elanousStateRoot());
|
|
443
450
|
const stageRoot = join(root, 'plugins', '.staging');
|
|
@@ -466,6 +473,10 @@ export async function installPlugin(spec: string, opts: InstallOptions = {}): Pr
|
|
|
466
473
|
const manifest: PluginManifest = loadPluginManifestFromDir(join(stage, 'package'), { id: source.expectedName ?? basename(spec) }).manifest;
|
|
467
474
|
if (!NAME.test(manifest.id) || !VERSION.test(manifest.version)) fail('io', 'invalid plugin name or version');
|
|
468
475
|
if (source.expectedName && (manifest.id !== source.expectedName || manifest.version !== source.expectedVersion)) fail('conflict', 'market name or version differs from manifest');
|
|
476
|
+
// [wiring] installPlugin is the caller: the PluginManifest normalized above by
|
|
477
|
+
// loadPluginManifestFromDir (present manifest or the loader's manifest-absent
|
|
478
|
+
// fallback) is handed into the install path here, past the NAME/VERSION gates.
|
|
479
|
+
const destination = pluginInstallPath(root, source.market, manifest);
|
|
469
480
|
const packageRoot = join(stage, 'package');
|
|
470
481
|
const mainPath = inside(packageRoot, manifest.main);
|
|
471
482
|
// A skills/graphs-only pack (official `elanous-basics`) declares no `main` — the default `./plugin.ts` is not a promise.
|
|
@@ -480,7 +491,6 @@ export async function installPlugin(spec: string, opts: InstallOptions = {}): Pr
|
|
|
480
491
|
const path = inside(packageRoot, asset);
|
|
481
492
|
if (!existsSync(path) || !realpathSync(path).startsWith(`${realpathSync(packageRoot)}${sep}`)) fail('io', `plugin asset not found or unsafe: ${asset}`);
|
|
482
493
|
}
|
|
483
|
-
const destination = join(root, 'plugins', source.market, name, version);
|
|
484
494
|
const emit = (event: InstallEvent) => opts.onEvent?.(event);
|
|
485
495
|
emit({ event: 'resolve', plugin: name, version, sha256: source.sha256 });
|
|
486
496
|
let security: ReturnType<typeof inspectPluginSecurity>;
|
|
@@ -393,6 +393,8 @@ export async function enhancePrompt(raw: string, opts: EnhanceOpts = {}): Promis
|
|
|
393
393
|
let complication: string | undefined;
|
|
394
394
|
let decisionSignal: EnhanceResult['decisionSignal'];
|
|
395
395
|
let enhancedBy: 'llm' | 'fallback' = 'llm';
|
|
396
|
+
let fallbackReason: 'llm-error' | 'empty-reply' | 'unparsed' | 'none' = 'none';
|
|
397
|
+
let replyChars = 0;
|
|
396
398
|
const groundedFacts = opts.groundedFacts ?? [];
|
|
397
399
|
const wantsScqa = groundedFacts.length > 0;
|
|
398
400
|
|
|
@@ -439,6 +441,10 @@ export async function enhancePrompt(raw: string, opts: EnhanceOpts = {}): Promis
|
|
|
439
441
|
try { opts.onUsage?.(usage); } catch { /* usage observation must not change enhancement */ }
|
|
440
442
|
},
|
|
441
443
|
});
|
|
444
|
+
replyChars = rawOut.length;
|
|
445
|
+
if (rawOut.trim().length === 0) {
|
|
446
|
+
fallbackReason = 'empty-reply';
|
|
447
|
+
}
|
|
442
448
|
const parsed = parseEnhanceJson(rawOut);
|
|
443
449
|
if (parsed) {
|
|
444
450
|
goal = parsed.goal;
|
|
@@ -449,9 +455,11 @@ export async function enhancePrompt(raw: string, opts: EnhanceOpts = {}): Promis
|
|
|
449
455
|
complication = parsed.complication;
|
|
450
456
|
decisionSignal = parsed.decisionSignal;
|
|
451
457
|
} else {
|
|
458
|
+
fallbackReason = fallbackReason === 'empty-reply' ? 'empty-reply' : 'unparsed';
|
|
452
459
|
enhancedBy = 'fallback';
|
|
453
460
|
}
|
|
454
461
|
} catch (e) {
|
|
462
|
+
fallbackReason = 'llm-error';
|
|
455
463
|
debug.log('prompt-enhance', 'llm-error', { error: String((e as { message?: string })?.message ?? e).slice(0, 160) }, { level: 'error' });
|
|
456
464
|
enhancedBy = 'fallback';
|
|
457
465
|
}
|
|
@@ -461,6 +469,7 @@ export async function enhancePrompt(raw: string, opts: EnhanceOpts = {}): Promis
|
|
|
461
469
|
checklist = extractChecklistFallback(original);
|
|
462
470
|
checklistProvenance = checklist.map(() => 'unknown');
|
|
463
471
|
enhancedBy = 'fallback';
|
|
472
|
+
if (fallbackReason === 'none') fallbackReason = 'unparsed';
|
|
464
473
|
}
|
|
465
474
|
if (!constraints.length) {
|
|
466
475
|
constraints = [
|
|
@@ -515,6 +524,8 @@ export async function enhancePrompt(raw: string, opts: EnhanceOpts = {}): Promis
|
|
|
515
524
|
enhancedChars: enhanced.length,
|
|
516
525
|
checklist: checklist.length,
|
|
517
526
|
enhancedBy,
|
|
527
|
+
fallbackReason,
|
|
528
|
+
replyChars,
|
|
518
529
|
model,
|
|
519
530
|
verbatimPreserved,
|
|
520
531
|
// ⭐ 관측 — 「요약을 요구했나 / 받았나 / 옮겨 적어 버렸나」를 «다른 값»으로 남긴다.
|
|
@@ -10,6 +10,8 @@ export type ChecklistDisposition = 'move' | 'known-issue' | 'block';
|
|
|
10
10
|
/** `screen` = a five-surface feature cell — its evidence carries a `짝:` line (MANUAL-five-surface-parity §A). `postpub` = measured after publication, outside the pre-publication gate. */
|
|
11
11
|
export type ChecklistKind = 'screen' | 'postpub';
|
|
12
12
|
const CHECKLIST_KINDS: readonly ChecklistKind[] = ['screen', 'postpub'];
|
|
13
|
+
export type ChecklistDispatchKind = 'code' | 'ops' | 'measure' | 'decision' | 'umbrella';
|
|
14
|
+
const CHECKLIST_DISPATCH_KINDS: readonly ChecklistDispatchKind[] = ['code', 'ops', 'measure', 'decision', 'umbrella'];
|
|
13
15
|
export interface ChecklistItem {
|
|
14
16
|
id: string;
|
|
15
17
|
title: string;
|
|
@@ -27,6 +29,8 @@ export interface ChecklistItem {
|
|
|
27
29
|
evidence?: string;
|
|
28
30
|
disposition?: ChecklistDisposition;
|
|
29
31
|
kind?: ChecklistKind;
|
|
32
|
+
/** Whether the seat dispatcher should launch a coding task. Missing in old rows means code. */
|
|
33
|
+
dispatchKind?: ChecklistDispatchKind;
|
|
30
34
|
updatedAt: string;
|
|
31
35
|
updatedBy: string;
|
|
32
36
|
}
|
|
@@ -122,6 +126,37 @@ function change(data: Checklist, id: string, field: string, from: unknown, to: u
|
|
|
122
126
|
|
|
123
127
|
const REPO_ROOT = join(import.meta.dir, '..', '..');
|
|
124
128
|
|
|
129
|
+
const PATH_TOKEN = /(?:^|[^A-Za-z0-9_.\/-])((?:(?:[A-Za-z0-9_.-]+\/)+)?[A-Za-z0-9_.-]+\.(?:ts|tsx|js|jsx|mjs|cjs|json|md|yaml|yml|sh|py|css|html|toml|sql|rs|swift))(?![A-Za-z0-9_.\/-])/g;
|
|
130
|
+
function mentionedPaths(text: string): string[] {
|
|
131
|
+
return [...text.matchAll(PATH_TOKEN)].map((match) => match[1]!).filter((path) => !path.split('/').includes('..'));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Shadow check at checklist authorship. Callers supply the origin/main file probe and merged-PR file reader. */
|
|
135
|
+
export function inspectChecklistTargetPaths(input: {
|
|
136
|
+
title: string; evidence?: string; refs?: readonly string[]; dispatchKind?: ChecklistDispatchKind;
|
|
137
|
+
}, deps: { existsAtMain: (path: string) => boolean; mergedPrFiles?: (number: number) => readonly string[] }): {
|
|
138
|
+
targets: string[]; warnings: string[]; candidates: string[];
|
|
139
|
+
} {
|
|
140
|
+
const lines = input.title.split(/\r?\n/);
|
|
141
|
+
const first = /^대상 경로:\s*(.*)$/.exec(lines[0] ?? '');
|
|
142
|
+
const targets = first ? [...new Set(mentionedPaths(first[1]!))] : [];
|
|
143
|
+
const newFileDeclared = /^신설:\s*/.test(lines[1] ?? '');
|
|
144
|
+
const warnings: string[] = [];
|
|
145
|
+
if ((input.dispatchKind ?? 'code') === 'code') {
|
|
146
|
+
if (!first || !first[1]!.trim() || targets.length === 0) warnings.push('첫 줄 대상 경로가 없다');
|
|
147
|
+
for (const path of targets) if (!deps.existsAtMain(path) && !newFileDeclared) warnings.push(`${path}: origin/main에 없는 파일 · 신설: 둘째 줄 선언 필요`);
|
|
148
|
+
}
|
|
149
|
+
if (!warnings.length) return { targets, warnings, candidates: [] };
|
|
150
|
+
const sources = [input.title, input.evidence ?? '', ...(input.refs ?? [])].join('\n');
|
|
151
|
+
const candidates = [...new Set(mentionedPaths(sources).filter(deps.existsAtMain))];
|
|
152
|
+
for (const match of sources.matchAll(/(?:^|[^\d])#(\d{2,8})\b/g)) {
|
|
153
|
+
for (const path of deps.mergedPrFiles?.(Number(match[1])) ?? []) {
|
|
154
|
+
if (deps.existsAtMain(path) && !candidates.includes(path)) candidates.push(path);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return { targets, warnings, candidates };
|
|
158
|
+
}
|
|
159
|
+
|
|
125
160
|
/** `--ref` 문서를 찾는 뿌리들 — 이 코드의 트리 ⊕ 지금 작업 디렉토리의 git 뿌리.
|
|
126
161
|
* ⭐ 설치본(`~/.local/share/elanous/current`)에는 `docs/` 가 없다 — 저장소 안에서 부른 설치본도 저장소 문서를 인용할 수 있게. */
|
|
127
162
|
export function refRoots(cwd = process.cwd()): string[] {
|
|
@@ -233,10 +268,11 @@ export function validateCeoLoad(input: { ceoMinutes?: number; ceoDate?: string }
|
|
|
233
268
|
if (input.ceoDate !== undefined && (!/^\d{4}-\d{2}-\d{2}$/.test(input.ceoDate) || !Number.isFinite(Date.parse(`${input.ceoDate}T00:00:00Z`)) || new Date(`${input.ceoDate}T00:00:00Z`).toISOString().slice(0, 10) !== input.ceoDate)) throw new CliUserError('대표 손 날짜는 YYYY-MM-DD 이어야 한다');
|
|
234
269
|
}
|
|
235
270
|
|
|
236
|
-
export function addItem(v: string, input: { id: string; title: string; refs?: string[]; owner?: string; kind?: ChecklistKind; priority?: ChecklistItem['priority']; predecessors?: string[]; deadlineVersion?: string; ceoMinutes?: number; ceoDate?: string; accelerator?: boolean }, options: { allowDuplicateId?: boolean } = {}): Checklist {
|
|
271
|
+
export function addItem(v: string, input: { id: string; title: string; refs?: string[]; owner?: string; kind?: ChecklistKind; dispatchKind?: ChecklistDispatchKind; priority?: ChecklistItem['priority']; predecessors?: string[]; deadlineVersion?: string; ceoMinutes?: number; ceoDate?: string; accelerator?: boolean }, options: { allowDuplicateId?: boolean } = {}): Checklist {
|
|
237
272
|
if (!input.id.trim()) throw new CliUserError('칸 id 가 비었다');
|
|
238
273
|
if (!input.title.trim()) throw new CliUserError('칸 제목이 비었다');
|
|
239
274
|
if (input.kind !== undefined && !CHECKLIST_KINDS.includes(input.kind)) throw new CliUserError(`잘못된 종류: ${input.kind}`, 'screen|postpub');
|
|
275
|
+
if (input.dispatchKind !== undefined && !CHECKLIST_DISPATCH_KINDS.includes(input.dispatchKind)) throw new CliUserError(`잘못된 발사 종류: ${input.dispatchKind}`, 'code|ops|measure|decision|umbrella');
|
|
240
276
|
if (input.owner !== undefined) parseOwner(input.owner);
|
|
241
277
|
if (input.priority !== undefined && !['P0', 'P1', 'P2'].includes(input.priority)) throw new CliUserError(`잘못된 우선순위: ${input.priority}`);
|
|
242
278
|
if (input.predecessors !== undefined && (!Array.isArray(input.predecessors) || input.predecessors.some((p) => typeof p !== 'string' || !p.trim() || p === input.id))) throw new CliUserError('잘못된 선행 칸');
|
|
@@ -255,20 +291,21 @@ export function addItem(v: string, input: { id: string; title: string; refs?: st
|
|
|
255
291
|
assertPredecessorsExist(input.predecessors, data, otherItems);
|
|
256
292
|
const by = process.env.ELANOUS_TRACK || 'cli';
|
|
257
293
|
const at = new Date().toISOString();
|
|
258
|
-
const item: ChecklistItem = { id: input.id, title: input.title, status: 'yellow', ...(input.owner !== undefined ? { owner: input.owner } : {}), ...(input.kind !== undefined ? { kind: input.kind } : {}), ...(input.priority !== undefined ? { priority: input.priority } : {}), ...(input.predecessors !== undefined ? { predecessors: input.predecessors } : {}), ...(input.deadlineVersion !== undefined ? { deadlineVersion: input.deadlineVersion } : {}), ...(input.ceoMinutes !== undefined ? { ceoMinutes: input.ceoMinutes } : {}), ...(input.ceoDate !== undefined ? { ceoDate: input.ceoDate } : {}), ...(input.accelerator === true ? { accelerator: true as const } : {}), ...(refs ? { refs } : {}), updatedAt: at, updatedBy: by };
|
|
294
|
+
const item: ChecklistItem = { id: input.id, title: input.title, status: 'yellow', ...(input.owner !== undefined ? { owner: input.owner } : {}), ...(input.kind !== undefined ? { kind: input.kind } : {}), ...(input.dispatchKind !== undefined ? { dispatchKind: input.dispatchKind } : {}), ...(input.priority !== undefined ? { priority: input.priority } : {}), ...(input.predecessors !== undefined ? { predecessors: input.predecessors } : {}), ...(input.deadlineVersion !== undefined ? { deadlineVersion: input.deadlineVersion } : {}), ...(input.ceoMinutes !== undefined ? { ceoMinutes: input.ceoMinutes } : {}), ...(input.ceoDate !== undefined ? { ceoDate: input.ceoDate } : {}), ...(input.accelerator === true ? { accelerator: true as const } : {}), ...(refs ? { refs } : {}), updatedAt: at, updatedBy: by };
|
|
259
295
|
data.items.push(item);
|
|
260
296
|
change(data, item.id, 'add', null, item, by, at);
|
|
261
297
|
return true;
|
|
262
298
|
});
|
|
263
299
|
}
|
|
264
300
|
|
|
265
|
-
export function setItem(v: string, id: string, patch: { status?: ChecklistStatus; evidence?: string; refs?: string[]; owner?: string; disposition?: ChecklistDisposition; kind?: ChecklistKind; priority?: ChecklistItem['priority']; predecessors?: string[]; deadlineVersion?: string; ceoMinutes?: number; ceoDate?: string; accelerator?: boolean | null }, by: string): Checklist {
|
|
301
|
+
export function setItem(v: string, id: string, patch: { status?: ChecklistStatus; evidence?: string; refs?: string[]; owner?: string; disposition?: ChecklistDisposition; kind?: ChecklistKind; dispatchKind?: ChecklistDispatchKind; priority?: ChecklistItem['priority']; predecessors?: string[]; deadlineVersion?: string; ceoMinutes?: number; ceoDate?: string; accelerator?: boolean | null }, by: string): Checklist {
|
|
266
302
|
return mutate(v, (data, otherItems) => {
|
|
267
303
|
const item = data.items.find((i) => i.id === id);
|
|
268
304
|
if (!item) throw new CliUserError(`없는 칸: ${id}`, 'list 로 칸 목록을 본다');
|
|
269
305
|
if (patch.status !== undefined && !['green', 'yellow', 'red', 'done'].includes(patch.status)) throw new CliUserError(`잘못된 상태: ${patch.status}`);
|
|
270
306
|
if (patch.disposition !== undefined && !['move', 'known-issue', 'block'].includes(patch.disposition)) throw new CliUserError(`잘못된 처분: ${patch.disposition}`);
|
|
271
307
|
if (patch.kind !== undefined && !CHECKLIST_KINDS.includes(patch.kind)) throw new CliUserError(`잘못된 종류: ${patch.kind}`, 'screen|postpub');
|
|
308
|
+
if (patch.dispatchKind !== undefined && !CHECKLIST_DISPATCH_KINDS.includes(patch.dispatchKind)) throw new CliUserError(`잘못된 발사 종류: ${patch.dispatchKind}`, 'code|ops|measure|decision|umbrella');
|
|
272
309
|
if (patch.priority !== undefined && !['P0', 'P1', 'P2'].includes(patch.priority)) throw new CliUserError(`잘못된 우선순위: ${patch.priority}`);
|
|
273
310
|
if (patch.predecessors !== undefined && (!Array.isArray(patch.predecessors) || patch.predecessors.some((p) => typeof p !== 'string' || !p.trim() || p === id))) throw new CliUserError('잘못된 선행 칸');
|
|
274
311
|
assertPredecessorsExist(patch.predecessors, data, otherItems);
|
|
@@ -283,7 +320,7 @@ export function setItem(v: string, id: string, patch: { status?: ChecklistStatus
|
|
|
283
320
|
throw new CliUserError(`지금 주인: ${item.owner} — --force 로만 바꾼다`, 'release checklist claim <id> --by <자리> --force');
|
|
284
321
|
}
|
|
285
322
|
}
|
|
286
|
-
const fields = (['evidence', 'owner', 'status', 'disposition', 'kind', 'priority', 'predecessors', 'deadlineVersion', 'ceoMinutes', 'ceoDate', 'accelerator', 'refs'] as const).filter((field) => patch[field] !== undefined && JSON.stringify(patch[field]) !== JSON.stringify(item[field] ?? null));
|
|
323
|
+
const fields = (['evidence', 'owner', 'status', 'disposition', 'kind', 'dispatchKind', 'priority', 'predecessors', 'deadlineVersion', 'ceoMinutes', 'ceoDate', 'accelerator', 'refs'] as const).filter((field) => patch[field] !== undefined && JSON.stringify(patch[field]) !== JSON.stringify(item[field] ?? null));
|
|
287
324
|
if (fields.length === 0) return false;
|
|
288
325
|
const at = new Date().toISOString();
|
|
289
326
|
for (const field of fields) {
|