elanous 0.2.11 → 0.2.12
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/1147-466ec15afacc1a65.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1394-556de6d71bbdb0fb.js → 1394-ee3795b112907122.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3869-3cd46526879f454c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7277-0b2fc7c16ba2d64b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7758-0b018a9e4fc56200.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8605-ea5bee9dfae512c7.js → 8605-0259994816604c49.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9920-5cfdc5e8fd209825.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-cb169620945d1952.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-6a061bd97f0b9398.js → page-cefe8169e36cf62a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/{page-cb95a7432c9ee744.js → page-d2e35228bc497119.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-b12b6d2f8d2d1aea.js → page-5c05b0da4bc1392c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-b9efe868bfd36255.js → page-e0ccdb576812b523.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-7c97dcdfe2e37a60.js → page-813c4a52c9d05ca8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-4731f2c215358937.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-5721537ca8a3598c.js → page-a022b257950f39e6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-b654240a572e4c48.js → page-a2ffc717e04b8b0a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-b9750abc897508c3.js → page-b59ee4e70fc18fcf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-a67e11ba962337b5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-c172dabd796eb7a3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-1efd0cb9e0ed9d13.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-c8b1ccda6ba7ae98.js → page-d5dc7ff69a37b4cf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-e71c84189e8c683e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/{page-2a9932ddb62d76bd.js → page-1d6a533ce5a6efcb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/market/{page-9688f5b4f1ff950f.js → page-dfca9b7d2de0b135.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-793fa536b3a6ef37.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-35414e0a007b4082.js → page-6dd3bfbbde2a5c2c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-e7a529e0507ac86c.js → not-found-490653e8d4cc8852.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/{page-e001823c35c9dd67.js → page-77447356baad75d2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-29e961191f6057df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-c822f55b884d9104.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fa4ccfc432424e52.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/{page-3a24072b35470157.js → page-6d604227e1664b71.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/page-9c7e8f744a765daf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-0be8d4b2ced75ba0.js → page-4048543b4b7ad5e0.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-d37cb0d52f9b6bd5.js → page-4c3a3126abed551a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-686da0df9bf23e5c.js → page-64b0aecb1ca75e75.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-4292d0149020e8a7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-53f80edc6b8dc07c.js → page-db14d0be7f1c460c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-e5d8072cd7cfef73.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-239c17b911b84e72.js → page-9547b5cec7cf76bf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-e6dcb83265530636.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-45248f08d6aecae5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-1218753c82d585f6.js → page-2cddeb9e2aeedc55.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/{page-4b5935e67531efb5.js → page-e297866051595d81.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/{page-3285bc87caf1eaa5.js → page-baba2e047872fb0f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-ad553b0e06505ecd.js → page-e895da4ec9cac345.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-4344faf20216233b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-698bb29f11864b89.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-bd6a6dc1ddc273f2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2e9e2fb01c9da1c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-d62ceb13411ebba4.js +1 -0
- package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +14 -14
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +14 -14
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +14 -14
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +14 -14
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +14 -14
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/graphs/context/nightly-condense.yaml +21 -0
- package/graphs/context/recipes.yaml +12 -0
- package/graphs/demo/inside-seed.yaml +20 -0
- package/graphs/demo/recipes.yaml +12 -0
- package/package.json +1 -1
- package/plugins/card-followup/examples/sample-card.png +0 -0
- package/scripts/lib/telegram-inject-args.ts +31 -0
- package/scripts/measure-pod-memory-by-goal.ts +130 -0
- package/scripts/release-loop/auto-approve-node.ts +1 -1
- package/scripts/release-loop/checklist-gate-node.ts +2 -1
- package/scripts/release-loop/gate-node.ts +8 -2
- package/scripts/release-loop/ops-upgrade-node.ts +47 -6
- package/scripts/release-loop/publish-node.ts +4 -1
- package/scripts/release-loop/version-node.ts +13 -3
- package/scripts/telegram-inject.ts +14 -6
- package/scripts/test-diet/remeasure-list.ts +112 -0
- package/scripts/ux/demo-rehearsal.ts +17 -13
- package/scripts/ux/demo-seed.ts +108 -0
- package/scripts/ux/lib/rehearsal-verdict.ts +12 -5
- package/src/agent-mission/driver.ts +138 -16
- package/src/agent-mission/pty-decision.ts +1 -1
- package/src/autopilot/pty-control-loop.ts +4 -1
- package/src/chat/index.ts +1 -0
- package/src/cli/persona-cli.ts +17 -14
- package/src/cli/pod-cli.ts +17 -0
- package/src/cli/pod-memory-advice.ts +35 -0
- package/src/cli/release-cli.ts +46 -6
- package/src/context-bus/context-now-surfaces.ts +17 -0
- package/src/context-bus/context-now.ts +2 -2
- package/src/context-bus/coord-events.ts +2 -2
- package/src/context-bus/nightly-condense.ts +153 -0
- package/src/context-bus/session-events.ts +35 -0
- package/src/dashboard/dashboard-status-lines.ts +2 -0
- package/src/dashboard/index.ts +8 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +24 -0
- package/src/domains/outbound-alert.ts +99 -31
- package/src/domains/outbound-log-probe.ts +8 -0
- package/src/execution-loop/launch-gate.ts +17 -8
- package/src/field-note/telegram.ts +142 -0
- package/src/graph-runner/graph-grow-llm.ts +23 -13
- package/src/graph-runner/graph-grow.ts +5 -2
- package/src/graph-runner/runner.ts +12 -8
- package/src/harness/harness-cli-command.ts +138 -16
- package/src/hooks/consult-intake.ts +153 -0
- package/src/hooks/receiver.ts +78 -1
- package/src/index.ts +33 -9
- package/src/intake-plane/discord-seat-work.ts +28 -2
- package/src/intake-plane/wish-card.ts +33 -0
- package/src/llm/difficulty.ts +1 -1
- package/src/maturity/feature-maturity.ts +2 -1
- package/src/nexus/api/http-server.ts +31 -8
- package/src/nexus/api/outbound-report.ts +40 -7
- package/src/nexus/api/personas.ts +95 -53
- package/src/nexus/api/task-cards-api.ts +19 -0
- package/src/nexus/api/workflow-history.ts +88 -0
- package/src/nexus/api/workflows.ts +52 -0
- package/src/nexus/outbound/delivery-ledger.ts +17 -8
- package/src/nexus/outbound/router.ts +8 -3
- package/src/persona/presets.ts +8 -1
- package/src/plugins/maker/market-bundle.ts +2 -1
- package/src/release-loop/checklist.ts +15 -3
- package/src/release-loop/feature-store.ts +24 -10
- package/src/release-loop/landed-but-yellow.ts +48 -0
- package/src/seat-loop/seat-loop.ts +168 -7
- package/src/self-dev/draft-sweep.ts +44 -10
- package/src/self-dev/draft-triage-rules.ts +54 -2
- package/src/self-implement/budget-gate.ts +29 -12
- package/src/self-implement/gate-candidate-ranking.ts +60 -0
- package/src/self-implement/gate-scope.ts +9 -8
- package/src/self-implement/headless-elanous-driver.ts +7 -2
- package/src/self-implement/orchestrator.ts +15 -0
- package/src/self-implement/test-seams.ts +1 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +14 -10
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +8 -0
- package/src/telegram-commands.ts +21 -0
- package/src/telegram-project-command.ts +78 -4
- package/src/telegram.ts +32 -5
- package/src/user-config.ts +8 -4
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/2310-7a59f043f6db207e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2548-6e316138d347cefe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3869-a39d1d9921e10d43.js +0 -1
- package/apps/pwa/out/_next/static/chunks/507-5acd98bd6dab2d8a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5445-745eb795c5963d79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-1dcdc58fc2de2454.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-8fd36984f549270a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-4cd1de371f8ad048.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-d99f89657828421c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-50cb2a0eb15e6c83.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-d6b1d156fb95e74b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-6b15dd6456c5df09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-371423d1a573d64b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4b764b125339ac17.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cdf8e4b01067bcf1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-94c82eb0ae5abb32.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-4dc614f3ce043e63.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-d02c782fc792e2af.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-dc6a79fb003f746c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-0fffb84cafdc9da6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-fca096e3e860306f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-9ebfa78f0d9885c3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-3dc5f21f7e39622d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-c6da2b468454532d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-93bc70e36f595414.js +0 -1
- package/apps/pwa/out/_next/static/css/543f56bcb1f79fca.css +0 -1
- /package/apps/pwa/out/_next/static/{Y4zKzL62sakG5momz3SoQ → -6XrAoD3eU7NyMHAE4xPS}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{Y4zKzL62sakG5momz3SoQ → -6XrAoD3eU7NyMHAE4xPS}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { readPodMemoryMeasurement, type MemoryMeasurement } from '../../scripts/measure-pod-memory-by-goal.js';
|
|
2
|
+
|
|
3
|
+
export type PodMemoryAdvice = {
|
|
4
|
+
window: MemoryMeasurement['window'];
|
|
5
|
+
sources: MemoryMeasurement['sources'];
|
|
6
|
+
byGoalType: Array<{
|
|
7
|
+
goalType: MemoryMeasurement['byGoalType'][number]['goalType'];
|
|
8
|
+
recommended: 'standard' | 'high';
|
|
9
|
+
limit: '16Gi' | '32Gi';
|
|
10
|
+
reason: string;
|
|
11
|
+
evidence: MemoryMeasurement['byGoalType'][number];
|
|
12
|
+
}>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** Advice only: the launch selector and its configuration remain independent. */
|
|
16
|
+
export function advisePodMemory(measurement: MemoryMeasurement): PodMemoryAdvice {
|
|
17
|
+
return {
|
|
18
|
+
window: measurement.window,
|
|
19
|
+
sources: measurement.sources,
|
|
20
|
+
byGoalType: measurement.byGoalType.map((evidence) => {
|
|
21
|
+
const high = evidence.oomKilled > 0 || (evidence.peakMiB.p95 !== null && evidence.peakMiB.p95 >= 16 * 1024);
|
|
22
|
+
const reason = evidence.oomKilled > 0
|
|
23
|
+
? `${evidence.oomKilled}/${evidence.runs} OOMKilled; measured peak p50=${evidence.peakMiB.p50 ?? 'n/a'} MiB, p95=${evidence.peakMiB.p95 ?? 'n/a'} MiB (${evidence.measured}/${evidence.runs} measured)`
|
|
24
|
+
: evidence.measured === 0
|
|
25
|
+
? `No measured peaks (${evidence.missingPeak}/${evidence.runs} missing); standard is provisional, not evidence of safety`
|
|
26
|
+
: `Measured peak p50=${evidence.peakMiB.p50} MiB, p95=${evidence.peakMiB.p95} MiB (${evidence.measured}/${evidence.runs} measured, ${evidence.missingPeak} missing); ${high ? 'at or above' : 'below'} 16Gi`;
|
|
27
|
+
return { goalType: evidence.goalType, recommended: high ? 'high' as const : 'standard' as const,
|
|
28
|
+
limit: high ? '32Gi' as const : '16Gi' as const, reason, evidence };
|
|
29
|
+
}),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function readPodMemoryAdvice(path?: string): PodMemoryAdvice {
|
|
34
|
+
return advisePodMemory(readPodMemoryMeasurement(path));
|
|
35
|
+
}
|
package/src/cli/release-cli.ts
CHANGED
|
@@ -26,6 +26,7 @@ import { envLiteral } from '../platform/env-literal.js';
|
|
|
26
26
|
import { userConfigPath } from '../user-config.js';
|
|
27
27
|
import { addItem, claimItem, decodeClaimHistoryEntry, devVersion, listChecklist, ownerMatches, parseOwner, parityGap, removeItem, seedFromRoadmap, setItem, summarizeChecklist, type ChecklistStatus, type ChecklistDisposition, type ChecklistKind } from '../release-loop/checklist.js';
|
|
28
28
|
import * as features from '../release-loop/feature-store.js';
|
|
29
|
+
import { landedButYellow, type MergedChecklistPr } from '../release-loop/landed-but-yellow.js';
|
|
29
30
|
import { formatSchedule, getSchedule, listSchedules, setSchedule } from '../release-loop/release-schedule.js';
|
|
30
31
|
import { writeStdoutJson } from './stdout-json.js';
|
|
31
32
|
import { CliUserError } from './cli-user-error.js';
|
|
@@ -499,7 +500,13 @@ async function checklistOutput(v: string, codenames: Record<string, string>, mod
|
|
|
499
500
|
}
|
|
500
501
|
}
|
|
501
502
|
|
|
502
|
-
export
|
|
503
|
+
export interface LandedButYellowDeps {
|
|
504
|
+
run?: Runner;
|
|
505
|
+
checklist?: typeof listChecklist;
|
|
506
|
+
now?: () => Date;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
export function registerReleaseCommands(program: Command, releaseRunDeps: UnattendedReleaseDeps = {}, landedDeps: LandedButYellowDeps = {}): void {
|
|
503
510
|
const release = program.command('release').description('공개 배포 한 판 — prepare(로컬) → publish(--yes) → verify (docs/manual/MANUAL-versioning-and-release-2026-09-25.md)');
|
|
504
511
|
const schedule = release.command('schedule').description('판별 컷·착지 마감 조회·갱신');
|
|
505
512
|
const scheduleLedgerRoot = () => releaseRunDeps.ledgerRoot ?? releaseLedgerRoot();
|
|
@@ -538,10 +545,43 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
538
545
|
const { version, codenames, json } = context(cmd);
|
|
539
546
|
await checklistOutput(version, codenames, 'status', json, opts.owner);
|
|
540
547
|
});
|
|
548
|
+
withContext(checklist.command('landed-but-yellow').description('병합 PR 이 있지만 노랑·빨강인 칸(읽기 전용)'))
|
|
549
|
+
.option('--owner <owner>', '자리 또는 하위 자리로 거르기')
|
|
550
|
+
.action(async (opts: { owner?: string }, cmd: Command) => {
|
|
551
|
+
const { version, json } = context(cmd);
|
|
552
|
+
try {
|
|
553
|
+
if (opts.owner !== undefined) parseOwner(opts.owner);
|
|
554
|
+
const since = new Date((landedDeps.now ?? (() => new Date()))().getTime() - 7 * 86_400_000).toISOString().slice(0, 10);
|
|
555
|
+
const raw = must((landedDeps.run ?? defaultRunner)('gh', ['pr', 'list', '--state', 'merged', '--search', `merged:>=${since}`, '--limit', '400', '--json', 'number,title,body,mergedAt'], process.cwd()), '병합 PR 조회(gh)');
|
|
556
|
+
const prs = JSON.parse(raw) as MergedChecklistPr[];
|
|
557
|
+
if (!Array.isArray(prs)) throw new Error('병합 PR 조회(gh): 배열이 아니다');
|
|
558
|
+
const items = (landedDeps.checklist ?? listChecklist)(version).items;
|
|
559
|
+
const rows = landedButYellow(items, prs, { owner: opts.owner });
|
|
560
|
+
if (json) await writeStdoutJson(`${JSON.stringify(rows)}\n`);
|
|
561
|
+
else {
|
|
562
|
+
const updatedAtById = new Map(items.map((item) => [item.id, item.updatedAt]));
|
|
563
|
+
const date = (iso: string) => {
|
|
564
|
+
const parts = new Intl.DateTimeFormat('en-US', { timeZone: 'Asia/Seoul', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hourCycle: 'h23' }).formatToParts(new Date(iso));
|
|
565
|
+
const part = (name: string) => parts.find((entry) => entry.type === name)?.value;
|
|
566
|
+
return `${part('month')}-${part('day')} ${part('hour')}:${part('minute')}`;
|
|
567
|
+
};
|
|
568
|
+
for (const row of rows) {
|
|
569
|
+
const icon = row.status === 'yellow' ? '🟡' : '🔴';
|
|
570
|
+
console.log(`${row.id} ${row.owner ?? '-'} ${icon} ← ${row.prs.map((pr, i) => `#${pr.number} «${pr.title}» (${date(pr.mergedAt)} · 근거에 ${row.alreadyInEvidence[i] ? '있음' : '없음'}${pr.mergedAt > (updatedAtById.get(row.id) ?? '') ? ' · 칸보다 새것' : ''})`).join(' · ')}`);
|
|
571
|
+
}
|
|
572
|
+
console.log(`${rows.length}칸 · 그중 근거에 없는 PR 이 있는 칸 ${rows.filter((row) => row.alreadyInEvidence.includes(false)).length}`);
|
|
573
|
+
}
|
|
574
|
+
if (prs.length >= 400) console.error('⚠ 병합 PR 400개 상한: 목록이 잘렸을 수 있다');
|
|
575
|
+
} catch (error) {
|
|
576
|
+
console.error(`⛔ ${error instanceof Error ? error.message : String(error)}`);
|
|
577
|
+
process.exitCode = 1;
|
|
578
|
+
}
|
|
579
|
+
});
|
|
541
580
|
withContext(checklist.command('add <id> <title>').description('칸 추가')).option('--owner <owner>', '담당').option('--kind <kind>', 'screen = 다섯 화면 짝 칸 · 근거에 짝: PWA … · 데스크톱 … · 폴드 … · 아이폰 … · 아이패드 … 한 줄')
|
|
542
|
-
.
|
|
581
|
+
.option('--allow-duplicate-id', '다른 판에 같은 id 가 있어도 경고 후 추가')
|
|
582
|
+
.action((id: string, title: string, opts: { owner?: string; kind?: string; allowDuplicateId?: boolean }, cmd: Command) => {
|
|
543
583
|
const { version, json } = context(cmd);
|
|
544
|
-
const data = addItem(version, { id, title, ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.kind !== undefined ? { kind: opts.kind as ChecklistKind } : {}) });
|
|
584
|
+
const data = addItem(version, { id, title, ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.kind !== undefined ? { kind: opts.kind as ChecklistKind } : {}) }, { allowDuplicateId: opts.allowDuplicateId });
|
|
545
585
|
if (json) console.log(JSON.stringify(data)); else console.log(`✅ ${id} 추가`);
|
|
546
586
|
});
|
|
547
587
|
withContext(checklist.command('set <id>').description('칸 상태·근거·담당·처분 갱신'))
|
|
@@ -581,11 +621,11 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
581
621
|
});
|
|
582
622
|
const actor = () => process.env.ELANOUS_TRACK || 'cli';
|
|
583
623
|
withContext(checklist.command('move <id>').description('칸을 한 트랜잭션으로 다른 판에 옮긴다'))
|
|
584
|
-
.requiredOption('--from <v>', '현재 판').requiredOption('--to <v>', '새 판')
|
|
585
|
-
.action((id: string, opts: { from: string; to: string }, cmd: Command) => {
|
|
624
|
+
.requiredOption('--from <v>', '현재 판').requiredOption('--to <v>', '새 판').option('--reason <reason>', '이동 사유')
|
|
625
|
+
.action((id: string, opts: { from: string; to: string; reason?: string }, cmd: Command) => {
|
|
586
626
|
const { codenames, json } = context(cmd);
|
|
587
627
|
const from = checklistVersion(opts.from, codenames), to = checklistVersion(opts.to, codenames);
|
|
588
|
-
features.move(id, from, to, actor());
|
|
628
|
+
features.move(id, from, to, actor(), undefined, undefined, opts.reason);
|
|
589
629
|
if (json) console.log(JSON.stringify(listChecklist(to))); else console.log(`✅ ${id} ${from} → ${to}`);
|
|
590
630
|
});
|
|
591
631
|
withContext(checklist.command('retitle <id> <title>').description('칸 제목 수정')).action((id: string, title: string, _opts: unknown, cmd: Command) => {
|
|
@@ -4,6 +4,23 @@ export function readSlashContextNow(args: string[], deps?: ContextNowDeps): Cont
|
|
|
4
4
|
return contextNow({ topic: args.join(' ').trim() }, deps);
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
export function seatsNowLine(answer: ContextNowAnswer, now: number): string | null {
|
|
8
|
+
const seats = [ ['OP', 'COO'], ['MK', 'CMO'], ['TC', 'CTO'], ['UX', 'CXO'] ] as const;
|
|
9
|
+
const facts = answer.facts.filter(fact => fact.kind === 'seat');
|
|
10
|
+
const lines = seats.flatMap(([seat, label]) => {
|
|
11
|
+
const latest = facts.filter(fact => fact.seat === seat)
|
|
12
|
+
.sort((a, b) => b.at.localeCompare(a.at))[0];
|
|
13
|
+
if (!latest) return [];
|
|
14
|
+
const minutes = Math.max(0, Math.floor((now - Date.parse(latest.at)) / 60_000));
|
|
15
|
+
const age = minutes >= 24 * 60 ? '하루 넘음'
|
|
16
|
+
: minutes >= 60 ? `${Math.floor(minutes / 60)}시간 전`
|
|
17
|
+
: minutes > 0 ? `${minutes}분 전` : '방금';
|
|
18
|
+
const title = latest.title?.split(/\r?\n/, 1)[0]?.trim();
|
|
19
|
+
return [`${label} ${title ? title.slice(0, 20) : latest.status} · ${age}`];
|
|
20
|
+
});
|
|
21
|
+
return lines.length ? `지금 자리들: ${lines.join(' | ')}` : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
7
24
|
function factText(fact: ContextFact): string {
|
|
8
25
|
switch (fact.kind) {
|
|
9
26
|
case 'version': return fact.version;
|
|
@@ -66,7 +66,7 @@ export function contextNow(options: { topic?: string; limit?: number } = {}, dep
|
|
|
66
66
|
? redactSecretText(entry.item.title.split(/\r?\n/, 1)[0] ?? '').slice(0, 120) : null, source });
|
|
67
67
|
}
|
|
68
68
|
const rawEvents = (deps.events ?? (since => listCoordEvents({ since })))(new Date(now.getTime() - 7 * 86_400_000).toISOString());
|
|
69
|
-
const chronologicalEvents = rawEvents.slice().sort((a, b) => a.at.localeCompare(b.at)
|
|
69
|
+
const chronologicalEvents = rawEvents.slice().sort((a, b) => a.at.localeCompare(b.at));
|
|
70
70
|
const matches = (text: string) => !topic || text.toLocaleLowerCase().includes(topic.toLocaleLowerCase());
|
|
71
71
|
const selectedFacts = facts.filter(f => !topic || (f.kind === 'cell' && matches(`${f.id} ${f.title}`))
|
|
72
72
|
|| (f.kind === 'seat' && matches(`${f.id ?? ''} ${f.title ?? ''}`)));
|
|
@@ -95,7 +95,7 @@ export function contextNow(options: { topic?: string; limit?: number } = {}, dep
|
|
|
95
95
|
source: event.refs.source ?? event.refs.url ?? `elanous://context/event/${encodeURIComponent(event.id)}`,
|
|
96
96
|
}));
|
|
97
97
|
const selectedEvents = summaries.filter(event => matches(event.summary)).reverse().slice(0, limit);
|
|
98
|
-
const guide = summaries.filter(event => event.kind === 'guide-changed' || event.summary.startsWith('📌안내'))
|
|
98
|
+
const guide = summaries.filter(event => event.kind === 'guide-changed' || event.kind === 'guidance-changed' || event.summary.startsWith('📌안내'))
|
|
99
99
|
.filter(event => matches(event.summary)).reverse().slice(0, limit)
|
|
100
100
|
.map(event => `📌 ${event.summary} — ${event.source}`);
|
|
101
101
|
const answer = { at, topic, facts: boundedFacts, events: selectedEvents, guide };
|
|
@@ -22,7 +22,7 @@ export interface CoordEvent {
|
|
|
22
22
|
text: string;
|
|
23
23
|
summary: string;
|
|
24
24
|
kind: string;
|
|
25
|
-
refs: Pick<CoordEventInput, 'seat' | 'recipients' | 'all' | 'kind' | 'slot' | 'deadline' | 'url'> & { origin?: string; source?: string };
|
|
25
|
+
refs: Pick<CoordEventInput, 'seat' | 'recipients' | 'all' | 'kind' | 'slot' | 'deadline' | 'url'> & { origin?: string; source?: string; ref?: string | null };
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
function seatIds(): Set<string> {
|
|
@@ -104,7 +104,7 @@ export function listCoordEvents(opts: { since: string; seat?: string }, deps: {
|
|
|
104
104
|
const db = deps.db ?? openSurfaceEventsDb();
|
|
105
105
|
try {
|
|
106
106
|
const rows = db.prepare(`SELECT id, ts, text, summary, kind, refs FROM events
|
|
107
|
-
WHERE surface IN ('coord:channel', 'context:external') AND direction='outbound' AND ts>=?
|
|
107
|
+
WHERE surface IN ('coord:channel', 'context:external', 'context:session') AND direction='outbound' AND ts>=?
|
|
108
108
|
${opts.seat ? "AND json_extract(refs,'$.seat')=?" : ''} ORDER BY ts ASC, rowid ASC`)
|
|
109
109
|
.all(...(opts.seat ? [opts.since, opts.seat] : [opts.since])) as Array<{
|
|
110
110
|
id: string; ts: string; text: string; summary: string; kind: string; refs: string;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, mkdirSync, writeFileSync, renameSync, existsSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { streamLLM } from '../llm.js';
|
|
4
|
+
import { condenseContextDay, type MemoryItem, type MemorySource, type MemorySummary } from './long-term-memory.js';
|
|
5
|
+
import { listCoordEvents, type CoordEvent } from './coord-events.js';
|
|
6
|
+
import { DecisionLedger, type DecisionEntry } from '../decisions/decision-ledger.js';
|
|
7
|
+
import { acquireLockAsync } from '../storage/file-lock.js';
|
|
8
|
+
|
|
9
|
+
type Stage = 'collect' | 'condense' | 'record' | 'conflict-candidates';
|
|
10
|
+
type Input = { day?: string; events?: CoordEvent[]; decisions?: DecisionEntry[]; summaries?: Record<string, MemorySummary> };
|
|
11
|
+
type Context = { graphId?: string; nodeId?: string; input: Input | null; outputs: Record<string, { day?: string; events?: CoordEvent[]; decisions?: DecisionEntry[]; items?: MemoryItem[] } | null> };
|
|
12
|
+
|
|
13
|
+
const dayBefore = (date: Date) => new Date(date.getTime() - 86_400_000).toISOString().slice(0, 10);
|
|
14
|
+
const fileName = (value: string) => encodeURIComponent(value).replace(/\./g, '%2E');
|
|
15
|
+
|
|
16
|
+
function snapshotPath(root: string, project: string, seat: string): string {
|
|
17
|
+
if (project === '.' || project === '..' || seat === '.' || seat === '..') throw new Error('invalid memory path');
|
|
18
|
+
return join(root, 'context-memory', fileName(project), `${fileName(seat)}.json`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function save(file: string, value: unknown): void {
|
|
22
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
23
|
+
const temp = `${file}.${process.pid}.tmp`;
|
|
24
|
+
writeFileSync(temp, JSON.stringify(value, null, 2) + '\n');
|
|
25
|
+
renameSync(temp, file);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function withCurrentDay<T>(root: string, day: string, write: () => T): Promise<T> {
|
|
29
|
+
const dir = join(root, 'context-memory');
|
|
30
|
+
mkdirSync(dir, { recursive: true });
|
|
31
|
+
const lock = await acquireLockAsync(join(dir, '.nightly-write.lock'));
|
|
32
|
+
try {
|
|
33
|
+
const marker = join(dir, 'latest-day');
|
|
34
|
+
const latest = latestDay(root);
|
|
35
|
+
if ((latest && latest > day) || storedItems(root).some(item =>
|
|
36
|
+
Date.parse(item.updatedAt) >= Date.parse(`${day}T00:00:00Z`) + 86_400_000)) {
|
|
37
|
+
throw new Error(`cannot replay ${day}: later project/seat memory exists`);
|
|
38
|
+
}
|
|
39
|
+
if (!lock.stillHeld()) throw new Error('nightly memory write lock lost');
|
|
40
|
+
// Reserve the day before touching either output, so a failed writer cannot let an older run overwrite it.
|
|
41
|
+
if (!latest || latest < day) save(marker, day);
|
|
42
|
+
return write();
|
|
43
|
+
} finally { lock.release(); }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function latestDay(root: string): string | undefined {
|
|
47
|
+
const marker = join(root, 'context-memory', 'latest-day');
|
|
48
|
+
return existsSync(marker) ? JSON.parse(readFileSync(marker, 'utf8')) as string : undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function storedItems(root: string): MemoryItem[] {
|
|
52
|
+
const dir = join(root, 'context-memory');
|
|
53
|
+
if (!existsSync(dir)) return [];
|
|
54
|
+
const items: MemoryItem[] = [];
|
|
55
|
+
for (const project of readdirSync(dir, { withFileTypes: true })) {
|
|
56
|
+
if (!project.isDirectory()) continue;
|
|
57
|
+
for (const seat of readdirSync(join(dir, project.name))) {
|
|
58
|
+
if (!seat.endsWith('.json')) continue;
|
|
59
|
+
const content: unknown = JSON.parse(readFileSync(join(dir, project.name, seat), 'utf8'));
|
|
60
|
+
if (!Array.isArray(content)) throw new Error('invalid context memory file');
|
|
61
|
+
items.push(...content as MemoryItem[]);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return items;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function summarize(source: MemorySource, injected?: Record<string, MemorySummary>): Promise<MemorySummary> {
|
|
68
|
+
if (injected) {
|
|
69
|
+
const result = injected[source.source];
|
|
70
|
+
if (!result) throw new Error(`missing test summary: ${source.source}`);
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
const response = await streamLLM([
|
|
74
|
+
{ role: 'system', content: 'Summarize this one-line context event as a compact project/seat memory item. Return ONLY JSON: {"project":"...","topic":"...","summary":"...","claim":{"key":"...","value":"..."}}. Omit claim when the event makes no comparable factual assertion. Never invent a project, topic or claim.' },
|
|
75
|
+
{ role: 'user', content: JSON.stringify(source) },
|
|
76
|
+
], () => {}, { maxTokens: 350, usageRole: 'research' });
|
|
77
|
+
const parsed: unknown = JSON.parse(response.replace(/^```(?:json)?\s*|\s*```$/g, '').trim());
|
|
78
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('invalid memory summary');
|
|
79
|
+
const value = parsed as Record<string, unknown>;
|
|
80
|
+
if (typeof value.project !== 'string' || typeof value.topic !== 'string' || typeof value.summary !== 'string' ||
|
|
81
|
+
(value.claim !== undefined && (!value.claim || typeof value.claim !== 'object' || Array.isArray(value.claim) ||
|
|
82
|
+
typeof (value.claim as Record<string, unknown>).key !== 'string' || typeof (value.claim as Record<string, unknown>).value !== 'string'))) {
|
|
83
|
+
throw new Error('invalid memory summary');
|
|
84
|
+
}
|
|
85
|
+
return value as unknown as MemorySummary;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Stage outputs travel only through the graph runner's per-run contexts. No decision ledger write occurs here. */
|
|
89
|
+
export async function runNightlyStage(stage: Stage, context: Context, root: string, dryRun: boolean, now = new Date()): Promise<Record<string, unknown>> {
|
|
90
|
+
const input = context.input ?? {};
|
|
91
|
+
if (dryRun && stage !== 'collect' && (!input.events || !input.decisions || !input.summaries)) {
|
|
92
|
+
throw new Error('dry-run requires fake events, decisions and summaries');
|
|
93
|
+
}
|
|
94
|
+
const day = context.outputs.collect?.day ?? input.day ?? dayBefore(now);
|
|
95
|
+
if (!/^\d{4}-\d\d-\d\d$/.test(day) || new Date(`${day}T00:00:00Z`).toISOString().slice(0, 10) !== day) {
|
|
96
|
+
throw new Error('invalid UTC day');
|
|
97
|
+
}
|
|
98
|
+
if (stage === 'collect') {
|
|
99
|
+
if (!dryRun && ((latestDay(root) ?? '') > day || storedItems(root).some(item =>
|
|
100
|
+
Date.parse(item.updatedAt) >= Date.parse(`${day}T00:00:00Z`) + 86_400_000))) {
|
|
101
|
+
throw new Error(`cannot replay ${day}: later project/seat memory exists`);
|
|
102
|
+
}
|
|
103
|
+
if (dryRun && (!input.events || !input.decisions || !input.summaries)) {
|
|
104
|
+
throw new Error('dry-run requires fake events, decisions and summaries');
|
|
105
|
+
}
|
|
106
|
+
const events = input.events ?? listCoordEvents({ since: new Date(Date.parse(`${day}T00:00:00Z`) - 86_400_000).toISOString() });
|
|
107
|
+
const decisions = input.decisions ?? (dryRun ? [] : new DecisionLedger().list({ status: 'all' }));
|
|
108
|
+
return { outcome: 'ok', day, events, decisions };
|
|
109
|
+
}
|
|
110
|
+
const collected = context.outputs.collect;
|
|
111
|
+
if (!collected?.day || !collected.events || !collected.decisions) throw new Error('collect output missing');
|
|
112
|
+
if (stage === 'condense') {
|
|
113
|
+
const items = await condenseContextDay(collected.day, dryRun ? [] : storedItems(root).filter(item => Date.parse(item.updatedAt) < Date.parse(`${collected.day}T00:00:00Z`)), {
|
|
114
|
+
events: () => collected.events!, decisions: () => collected.decisions!,
|
|
115
|
+
summarize: source => summarize(source, input.summaries),
|
|
116
|
+
}, now);
|
|
117
|
+
return { outcome: 'ok', items };
|
|
118
|
+
}
|
|
119
|
+
const items = context.outputs.condense?.items;
|
|
120
|
+
if (!items) throw new Error('condense output missing');
|
|
121
|
+
if (stage === 'record') {
|
|
122
|
+
const grouped = new Map<string, MemoryItem[]>();
|
|
123
|
+
for (const item of items) {
|
|
124
|
+
const file = snapshotPath(root, item.project, item.seat);
|
|
125
|
+
grouped.set(file, [...grouped.get(file) ?? [], item]);
|
|
126
|
+
}
|
|
127
|
+
if (!dryRun) await withCurrentDay(root, day, () => {
|
|
128
|
+
for (const [file, group] of grouped) save(file, group);
|
|
129
|
+
});
|
|
130
|
+
return { outcome: 'ok', files: [...grouped.keys()], items: items.length, memoryItems: dryRun ? items : undefined, dryRun };
|
|
131
|
+
}
|
|
132
|
+
if (stage === 'conflict-candidates') {
|
|
133
|
+
if (!context.outputs.record) throw new Error('record output missing');
|
|
134
|
+
const candidates = items.filter(item => item.conflict && item.status === 'active').map(item => ({
|
|
135
|
+
project: item.project, topic: item.topic, owner: item.conflict!.owner,
|
|
136
|
+
sources: item.conflict!.sources, status: 'candidate' as const,
|
|
137
|
+
}));
|
|
138
|
+
if (!dryRun) await withCurrentDay(root, day, () => save(join(root, 'context-memory', 'conflict-candidates.json'), candidates));
|
|
139
|
+
return { outcome: 'ok', candidates, published: 0 };
|
|
140
|
+
}
|
|
141
|
+
throw new Error(`unknown stage: ${stage}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (import.meta.main) {
|
|
145
|
+
const stage = process.argv[2] as Stage;
|
|
146
|
+
const path = process.env.ELANOUS_GRAPH_CONTEXT;
|
|
147
|
+
if (!path || !['collect', 'condense', 'record', 'conflict-candidates'].includes(stage)) throw new Error('invalid nightly graph stage');
|
|
148
|
+
const context = JSON.parse(readFileSync(path, 'utf8')) as Context;
|
|
149
|
+
if (context.graphId !== 'nightly-condense' || context.nodeId !== stage) throw new Error('nightly graph context mismatch');
|
|
150
|
+
const statePath = resolve(path, '..', '..', '..', '..');
|
|
151
|
+
const result = await runNightlyStage(stage, context, statePath, process.env.ELANOUS_GRAPH_DRY_RUN === '1');
|
|
152
|
+
console.log(JSON.stringify(result));
|
|
153
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
import { redactSecretText } from '../debug/log.js';
|
|
3
|
+
import { openSurfaceEventsDb, recordEvent } from '../domains/surface-events.js';
|
|
4
|
+
|
|
5
|
+
export const SESSION_EVENT_KINDS = ['task-claimed', 'task-done', 'asked', 'guidance-changed'] as const;
|
|
6
|
+
export type SessionEventKind = typeof SESSION_EVENT_KINDS[number];
|
|
7
|
+
|
|
8
|
+
export interface SessionEventInput {
|
|
9
|
+
kind: SessionEventKind;
|
|
10
|
+
seat: string;
|
|
11
|
+
text: string;
|
|
12
|
+
ref?: string;
|
|
13
|
+
at?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Publish a one-line external-agent session signal to the existing context journal. */
|
|
17
|
+
export function emitSessionEvent(input: SessionEventInput, deps: { db?: Database } = {}): string {
|
|
18
|
+
if (!SESSION_EVENT_KINDS.includes(input.kind)) throw new Error('unknown context session kind');
|
|
19
|
+
if (!['OP', 'TC', 'MK', 'UX'].includes(input.seat)) throw new Error('context session seat must be OP|TC|MK|UX');
|
|
20
|
+
if (/\r|\n/.test(input.text) || !input.text.trim()) throw new Error('context session text must be one nonempty line');
|
|
21
|
+
if (input.ref !== undefined && (/\r|\n/.test(input.ref) || !input.ref.trim())) throw new Error('context session ref must be one nonempty line');
|
|
22
|
+
const summary = redactSecretText(input.text).trim().slice(0, 120);
|
|
23
|
+
const ref = input.ref ?? null;
|
|
24
|
+
const db = deps.db ?? openSurfaceEventsDb();
|
|
25
|
+
try {
|
|
26
|
+
return recordEvent(db, {
|
|
27
|
+
surface: 'context:session', direction: 'outbound', domain: 'elanous', category: 'coordination',
|
|
28
|
+
kind: input.kind, text: input.text, summary,
|
|
29
|
+
refs: JSON.stringify({ seat: input.seat, recipients: [], all: false, kind: input.kind,
|
|
30
|
+
slot: null, deadline: null, url: null, ref,
|
|
31
|
+
...(ref ? { source: `elanous://context/ref/${encodeURIComponent(ref)}` } : {}) }),
|
|
32
|
+
...(input.at ? { ts: input.at } : {}),
|
|
33
|
+
});
|
|
34
|
+
} finally { if (!deps.db) db.close(); }
|
|
35
|
+
}
|
|
@@ -5,6 +5,7 @@ export interface DashboardStatusLinesInput {
|
|
|
5
5
|
readonly daemonAddress?: string | null;
|
|
6
6
|
readonly daemonConnected: boolean;
|
|
7
7
|
readonly accountName?: string | null;
|
|
8
|
+
readonly seatsNow?: string | null;
|
|
8
9
|
readonly sessionSurfaceLines: readonly string[];
|
|
9
10
|
readonly view: string;
|
|
10
11
|
readonly focus: string;
|
|
@@ -31,6 +32,7 @@ export function buildDashboardStatusLines(input: DashboardStatusLinesInput): str
|
|
|
31
32
|
`모델: ${known(input.provider)}/${known(input.model)} (${known(input.reasoning)})`,
|
|
32
33
|
`연결: 데몬 ${address} ${input.daemonConnected ? '붙음' : '안 붙음'}`,
|
|
33
34
|
`계정: ${input.accountName?.trim() === 'default' ? '기본' : known(input.accountName)}`,
|
|
35
|
+
...(input.seatsNow ? [input.seatsNow] : []),
|
|
34
36
|
'',
|
|
35
37
|
'자세히',
|
|
36
38
|
...input.sessionSurfaceLines.map(line => ` ${line}`),
|
package/src/dashboard/index.ts
CHANGED
|
@@ -884,6 +884,7 @@ import { getInputHistoryStore, inputHistoryDbPath, inputHistoryJsonPath } from '
|
|
|
884
884
|
import { inspectActiveProvider } from '../provider-summary.js';
|
|
885
885
|
import { activeCodexAccountView } from '../oauth/codex-account-store.js';
|
|
886
886
|
import { buildDashboardStatusLines } from './dashboard-status-lines.js';
|
|
887
|
+
import { readSlashContextNow, seatsNowLine } from '../context-bus/context-now-surfaces.js';
|
|
887
888
|
import { buildFirstScreenBand } from './first-screen-band.js';
|
|
888
889
|
import { runNexusShow, type NexusShowResult } from '../cli/nexus-show.js';
|
|
889
890
|
import {
|
|
@@ -6405,7 +6406,14 @@ Mode- and sync-specific instructions are injected per-turn when relevant — do
|
|
|
6405
6406
|
const active = inspectActiveProvider(cfg);
|
|
6406
6407
|
const reasoningProvider = (['openai-codex', 'openai', 'anthropic', 'gemini', 'grok', 'local', 'openrouter'] as const)
|
|
6407
6408
|
.find(provider => active.provider === provider || active.provider === `auto:${provider}`);
|
|
6409
|
+
let seatsNow: string | null = null;
|
|
6410
|
+
try {
|
|
6411
|
+
seatsNow = seatsNowLine(readSlashContextNow([]), Date.now());
|
|
6412
|
+
} catch (error) {
|
|
6413
|
+
debug.log('dashboard.status', 'seats-unreadable', { error: String(error) });
|
|
6414
|
+
}
|
|
6408
6415
|
return buildDashboardStatusLines({
|
|
6416
|
+
seatsNow,
|
|
6409
6417
|
provider: active.provider,
|
|
6410
6418
|
model: active.model,
|
|
6411
6419
|
reasoning: reasoningProvider
|
|
@@ -273,6 +273,8 @@
|
|
|
273
273
|
// of circular dependency on dashboard/index.ts.
|
|
274
274
|
|
|
275
275
|
import { tuiNowSlash } from '../../context-bus/context-now-surfaces.js';
|
|
276
|
+
import { createWishCard } from '../../intake-plane/wish-card.js';
|
|
277
|
+
import { CardStore } from '../../task-cards/card-store.js';
|
|
276
278
|
import type { ContextNowDeps } from '../../context-bus/context-now.js';
|
|
277
279
|
import { runRebindCommand } from '../../input-core/index.js';
|
|
278
280
|
// B4 (TUI half) — /design reuses the CLI's own resolution so the three
|
|
@@ -422,6 +424,8 @@ export interface DashboardSlashContext {
|
|
|
422
424
|
/** Existing TUI structured-question capability. Absent means /ask keeps
|
|
423
425
|
* the self-dev noninteractive deferred-clarification contract. */
|
|
424
426
|
surfaceUx?: Pick<SurfaceUx, 'question'>;
|
|
427
|
+
/** Optional wish-card store for isolated slash dispatch tests. */
|
|
428
|
+
wishCardStore?: CardStore;
|
|
425
429
|
attachmentRowMap: { clear(): void };
|
|
426
430
|
pushDebugLine(line: string): void;
|
|
427
431
|
pushChatLine(line: string): void;
|
|
@@ -1367,6 +1371,26 @@ export function buildDashboardSlashRegistry(nowDeps?: ContextNowDeps): SlashComm
|
|
|
1367
1371
|
for (const line of tuiNowSlash(args, nowDeps)) ctx.pushChatLine(line);
|
|
1368
1372
|
});
|
|
1369
1373
|
|
|
1374
|
+
registry.register('wish', (args, ctx) => {
|
|
1375
|
+
const text = args.join(' ').trim();
|
|
1376
|
+
if (!text) {
|
|
1377
|
+
ctx.pushChatLine(ctx.warning(' usage: /wish <소원 한 줄>'));
|
|
1378
|
+
ctx.setChatScrollOffset(-1);
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
let store: CardStore | undefined;
|
|
1382
|
+
try {
|
|
1383
|
+
store = ctx.wishCardStore ?? new CardStore();
|
|
1384
|
+
const card = createWishCard({ text, source: 'tui', ref: crypto.randomUUID() }, store);
|
|
1385
|
+
ctx.pushChatLine(ctx.success(` 소원 카드로 남겼습니다 — ${card.title}`));
|
|
1386
|
+
} catch (err) {
|
|
1387
|
+
ctx.pushChatLine(ctx.error(` 소원 카드 등록 실패 — ${err instanceof Error ? err.message : String(err)}`));
|
|
1388
|
+
} finally {
|
|
1389
|
+
if (!ctx.wishCardStore) store?.close();
|
|
1390
|
+
}
|
|
1391
|
+
ctx.setChatScrollOffset(-1);
|
|
1392
|
+
});
|
|
1393
|
+
|
|
1370
1394
|
// ── B-1.a pilot ────────────────────────────────────────────────────
|
|
1371
1395
|
registry.register(['quit', 'q', 'exit'], (_args, ctx) => {
|
|
1372
1396
|
(ctx.exitTui ?? ctx.closeTui)();
|