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,108 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
export type SeedArgs = { only: 'graph' | 'wizard' | 'all'; dryRun: boolean; json: boolean };
|
|
5
|
+
export type SeedEvent = { target: 'graph' | 'wizard'; phase: 'start' | 'finish' | 'failure'; command: string[]; exitCode?: number; output?: string };
|
|
6
|
+
|
|
7
|
+
export const SCENE_SIX_GUIDANCE = '⑥ PTY 인텔리전스는 이 시드로 생성하지 않습니다. 실제 격리 미션을 실행하고 read → judge → answer 이벤트를 관측하세요.';
|
|
8
|
+
const GRAPH = 'graphs/demo/inside-seed.yaml';
|
|
9
|
+
const WIZARD_REQUEST = '데모용 자료 조사 커넥터와 스킬·그래프 초안을 만들어 주세요. 설치하거나 실행하지 마세요.';
|
|
10
|
+
const DRAFT = { description: 'Demo research draft only; no connector credentials or outbound messages.', connectors: [],
|
|
11
|
+
skill: { description: 'Demo research draft', instructions: 'Research draft only. Implement and validate processing before installing or running.', requires: [] } };
|
|
12
|
+
|
|
13
|
+
export function parseArgs(argv: string[]): SeedArgs {
|
|
14
|
+
const result: SeedArgs = { only: 'all', dryRun: false, json: false };
|
|
15
|
+
const seen = new Set<string>();
|
|
16
|
+
for (let i = 0; i < argv.length; i++) {
|
|
17
|
+
const flag = argv[i]!;
|
|
18
|
+
if (!['--only', '--dry-run', '--json'].includes(flag)) throw new Error(`알 수 없는 인자: ${flag}`);
|
|
19
|
+
if (seen.has(flag)) throw new Error(`중복 인자: ${flag}`);
|
|
20
|
+
seen.add(flag);
|
|
21
|
+
if (flag === '--only') {
|
|
22
|
+
const value = argv[++i];
|
|
23
|
+
if (value !== 'all' && value !== 'graph' && value !== 'wizard') throw new Error('--only 값은 graph|wizard|all 이어야 합니다');
|
|
24
|
+
result.only = value;
|
|
25
|
+
} else if (flag === '--dry-run') result.dryRun = true;
|
|
26
|
+
else result.json = true;
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type SeedDeps = {
|
|
32
|
+
repo: string;
|
|
33
|
+
run: (command: string[], cwd: string) => Promise<{ exitCode: number; output: string; stdout?: string }>;
|
|
34
|
+
prepareDraft: (repo: string) => { file: string; parent: string };
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function prepareDraft(repo: string): { file: string; parent: string } {
|
|
38
|
+
const root = resolve(repo, '.elanous-test', 'demo-seed');
|
|
39
|
+
mkdirSync(root, { recursive: true });
|
|
40
|
+
const parent = mkdtempSync(join(root, 'wizard-'));
|
|
41
|
+
const file = join(parent, 'research-draft.json');
|
|
42
|
+
writeFileSync(file, JSON.stringify(DRAFT) + '\n');
|
|
43
|
+
return { file, parent };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const defaultDeps: SeedDeps = {
|
|
47
|
+
repo: resolve(import.meta.dir, '../..'),
|
|
48
|
+
prepareDraft,
|
|
49
|
+
run: async (command, cwd) => {
|
|
50
|
+
const child = Bun.spawn(command, { cwd, env: { ...process.env, NODE_ENV: 'test' }, stdout: 'pipe', stderr: 'pipe' });
|
|
51
|
+
const [output, errors, exitCode] = await Promise.all([new Response(child.stdout).text(), new Response(child.stderr).text(), child.exited]);
|
|
52
|
+
return { exitCode, stdout: output, output: [output, errors].filter(Boolean).join('\n').trim() };
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export async function seed(args: SeedArgs, deps: SeedDeps = defaultDeps, onEvent?: (event: SeedEvent) => void): Promise<{ events: SeedEvent[]; guidance: string; ok: boolean }> {
|
|
57
|
+
const events: SeedEvent[] = [];
|
|
58
|
+
const emit = (event: SeedEvent): void => { events.push(event); onEvent?.(event); };
|
|
59
|
+
let ok = true;
|
|
60
|
+
for (const target of (args.only === 'all' ? ['graph', 'wizard'] : [args.only]) as Array<'graph' | 'wizard'>) {
|
|
61
|
+
const base = ['bun', 'bin/elanous.mjs', '--test'];
|
|
62
|
+
const graphCommand = [...base, 'graph', 'run', GRAPH, '--json'];
|
|
63
|
+
const wizardCommand = [...base, 'plugin', 'make', WIZARD_REQUEST, '--name', 'inside-demo-draft', '--draft-file', '<isolated-draft-file>', '--dir', '<isolated-parent>', '--json'];
|
|
64
|
+
const command = target === 'graph' ? graphCommand : wizardCommand;
|
|
65
|
+
if (args.dryRun) {
|
|
66
|
+
emit({ target, phase: 'start', command });
|
|
67
|
+
emit({ target, phase: 'finish', command, exitCode: 0, output: 'dry-run: no process started' });
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
if (target === 'wizard') {
|
|
72
|
+
const { file, parent } = deps.prepareDraft(deps.repo);
|
|
73
|
+
command.splice(command.indexOf('<isolated-draft-file>'), 1, file);
|
|
74
|
+
command.splice(command.indexOf('<isolated-parent>'), 1, parent);
|
|
75
|
+
}
|
|
76
|
+
emit({ target, phase: 'start', command });
|
|
77
|
+
const result = await deps.run(command, deps.repo);
|
|
78
|
+
const parsed = (() => {
|
|
79
|
+
try { return JSON.parse(result.stdout ?? result.output) as { status?: string; dryRun?: boolean; errors?: string[]; timings?: { install?: number } }; } catch { return null; }
|
|
80
|
+
})();
|
|
81
|
+
const complete = target === 'wizard'
|
|
82
|
+
? parsed?.status === 'draft' && parsed.timings?.install === 0 && parsed.errors?.length === 0
|
|
83
|
+
: parsed?.status === 'done' && parsed.dryRun === false;
|
|
84
|
+
const phase = result.exitCode === 0 && complete ? 'finish' : 'failure';
|
|
85
|
+
emit({ target, phase, command, exitCode: result.exitCode, output: result.output });
|
|
86
|
+
if (phase === 'failure') ok = false;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
ok = false;
|
|
89
|
+
emit({ target, phase: 'failure', command, output: error instanceof Error ? error.message : String(error) });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return { events, guidance: SCENE_SIX_GUIDANCE, ok };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (import.meta.main) {
|
|
96
|
+
try {
|
|
97
|
+
const args = parseArgs(process.argv.slice(2));
|
|
98
|
+
const report = await seed(args, defaultDeps, args.json ? undefined : (event) => {
|
|
99
|
+
console.log(`${event.target} ${event.phase}${event.exitCode === undefined ? '' : ` (exit ${event.exitCode})`}: ${event.command.join(' ')}${event.output ? `\n${event.output}` : ''}`);
|
|
100
|
+
});
|
|
101
|
+
if (args.json) console.log(JSON.stringify(report));
|
|
102
|
+
else console.log(report.guidance);
|
|
103
|
+
if (!report.ok) process.exitCode = 1;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
106
|
+
process.exitCode = 1;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -10,6 +10,8 @@ export type SceneObservation = {
|
|
|
10
10
|
leaks: string[];
|
|
11
11
|
sectionsInDom: number;
|
|
12
12
|
visibleScene: number | null;
|
|
13
|
+
/** Empty-state phrases observed in the visible scene. */
|
|
14
|
+
emptyStates?: string[];
|
|
13
15
|
/** Number of captured JPEG frames with no discernible scene content. */
|
|
14
16
|
blankFrames?: number;
|
|
15
17
|
};
|
|
@@ -36,7 +38,7 @@ export function failedApiResponse(url: string, status: number): string | null {
|
|
|
36
38
|
} catch { return null; }
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
export type SceneResult = SceneObservation & { verdict: 'ok' | 'broken' | 'unverified'; reasons: string[] };
|
|
41
|
+
export type SceneResult = SceneObservation & { verdict: 'ok' | 'broken' | 'unverified' | 'no-data'; reasons: string[] };
|
|
40
42
|
|
|
41
43
|
/** A scene the demo hides is not a pass — it was not seen. OP 10-03: ⑤ (wizard → market) is the 10-08 highlight. */
|
|
42
44
|
export const UNVERIFIED_REASON = '시연 모드에서 숨긴 장면 — WIZ1 착지 뒤 실물 필요';
|
|
@@ -58,16 +60,21 @@ export function judgeScene(obs: SceneObservation): SceneResult {
|
|
|
58
60
|
if (obs.sectionsInDom < 6) reasons.push(`장면 언마운트 (${obs.sectionsInDom}/6)`);
|
|
59
61
|
if (obs.visibleScene !== obs.scene) reasons.push(`보이는 장면 불일치 (기대 ${obs.scene}, 실제 ${obs.visibleScene ?? '없음'})`);
|
|
60
62
|
if (obs.leaks.length) reasons.push(`공개 화면 누설 ${obs.leaks.length}건`);
|
|
61
|
-
return { ...obs, verdict:
|
|
63
|
+
if (reasons.length) return { ...obs, verdict: 'broken', reasons };
|
|
64
|
+
if (obs.emptyStates?.length) return { ...obs, verdict: 'no-data', reasons: obs.emptyStates.map((phrase) => `실데이터 없음 — ${phrase}`) };
|
|
65
|
+
return { ...obs, verdict: 'ok', reasons };
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
export function judgeRun(scenes: SceneResult[]) {
|
|
65
69
|
const ok = scenes.filter((scene) => scene.verdict === 'ok').length;
|
|
66
70
|
const broken = scenes.filter((scene) => scene.verdict === 'broken').length;
|
|
67
71
|
const unverified = scenes.filter((scene) => scene.verdict === 'unverified').length;
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
const noData = scenes.filter((scene) => scene.verdict === 'no-data').length;
|
|
73
|
+
// Broken wins, then unseen scenes, then scenes without real data; none is a pass.
|
|
74
|
+
const verdict = broken ? 'broken' as const : unverified ? 'unverified' as const : noData ? 'no-data' as const : 'ok' as const;
|
|
70
75
|
const banner = scenes.filter((scene) => scene.verdict === 'unverified')
|
|
71
76
|
.map((scene) => `${CIRCLED[scene.scene - 1] ?? scene.scene} 미검증 — WIZ1 착지 뒤 실물 필요`);
|
|
72
|
-
|
|
77
|
+
const noDataScenes = scenes.filter((scene) => scene.verdict === 'no-data').map((scene) => CIRCLED[scene.scene - 1] ?? String(scene.scene));
|
|
78
|
+
if (noDataScenes.length) banner.push(`${noDataScenes.join('')} 실데이터 없음 — 런·PTY·마법사가 도는 판에서 다시`);
|
|
79
|
+
return { verdict, ok, broken, unverified, noData, banner, scenes };
|
|
73
80
|
}
|
|
@@ -22,8 +22,10 @@ import { streamLLM, type LLMMessage } from '../llm.js';
|
|
|
22
22
|
import { debug } from '../debug/log.js';
|
|
23
23
|
import { emitPtyDecision, type DecisionInput } from './pty-decision.js';
|
|
24
24
|
import { observeExternalEvent } from '../context-bus/external-events.js';
|
|
25
|
+
import { emitSessionEvent, type SessionEventInput } from '../context-bus/session-events.js';
|
|
25
26
|
import { emitDecision } from '../live/detail-switch.js';
|
|
26
27
|
import { resolvePtyWebAddress } from '../cli/pty-web-address.js';
|
|
28
|
+
import { verdictForFinalPtyScreen } from '../cli/pty-drive-verdict.js';
|
|
27
29
|
import { reemitPtyUsage } from '../budget/pty-usage-reemit.js';
|
|
28
30
|
import { classifyAuthError } from '../oauth/codex.js';
|
|
29
31
|
import type { FallbackStep } from '../oauth/fallback-chain.js';
|
|
@@ -451,6 +453,8 @@ export interface AgentMissionSpec {
|
|
|
451
453
|
evidence: EvidenceMode;
|
|
452
454
|
/** RFC 최대 라운드(기본 16). */
|
|
453
455
|
maxRounds?: number;
|
|
456
|
+
/** 화면 무변화 회복 문턱(ms, 기본 45초). */
|
|
457
|
+
recoverAfterMs?: number;
|
|
454
458
|
/** 완료 시 자동 commit(기본 true). */
|
|
455
459
|
commit?: boolean;
|
|
456
460
|
/** 스크린 캡처 저장 디렉토리. */
|
|
@@ -538,6 +542,12 @@ export interface AgentMissionDeps {
|
|
|
538
542
|
checkEvidence?: typeof checkEvidence;
|
|
539
543
|
commitWorktree?: typeof commitWorktree;
|
|
540
544
|
dispatchOpenPullRequest?: typeof dispatchOpenPullRequest;
|
|
545
|
+
/** Optional context-journal sink for isolated missions. */
|
|
546
|
+
emitContextEvent?: (input: SessionEventInput) => void;
|
|
547
|
+
/** Internal continuation retains the original mission's context-event owner. */
|
|
548
|
+
contextEventRun?: boolean;
|
|
549
|
+
/** Carries a PR number back to the outer mission's context event. */
|
|
550
|
+
onMissionPrNumber?: (number: number) => void;
|
|
541
551
|
terminateProcessTree?: typeof terminateMissionProcessTree;
|
|
542
552
|
repoStatus?: (repo: string) => string | Promise<string>;
|
|
543
553
|
emitDecision?: typeof emitDecision;
|
|
@@ -835,18 +845,36 @@ export interface MissionControlBrainOpts {
|
|
|
835
845
|
/** Screen-grounded brain summary for the next backend, never the raw PTY screen. */
|
|
836
846
|
readonly onSummary?: (summary: string) => void;
|
|
837
847
|
readonly onDecision?: (text: string, reply?: { question: string; answer: string }) => void;
|
|
848
|
+
readonly recoverAfterMs?: number;
|
|
849
|
+
readonly initialCommand?: string;
|
|
850
|
+
readonly onRecover?: (blocked: string, action: string) => void;
|
|
838
851
|
}
|
|
839
852
|
|
|
840
853
|
/** 기존 5-action mission 판단을 canonical PTY control-loop 결정으로 축소한다. search는 이 경계에서만
|
|
841
854
|
* 부작용(context 기록)을 끝내고, child에는 context를 읽고 계속하라는 단일 input만 전달한다. */
|
|
842
|
-
export
|
|
855
|
+
export type MissionControlBrain = RunSupervisor & {
|
|
856
|
+
/** Report whether the last returned input was accepted by the PTY; recovery stages and events commit only on acceptance. */
|
|
857
|
+
readonly onInputResult: (text: string, accepted: boolean) => void;
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
export function createMissionControlBrain(opts: MissionControlBrainOpts): MissionControlBrain {
|
|
843
861
|
const history: string[] = [];
|
|
844
862
|
const backendName = (opts.backend ?? codexBackend).name;
|
|
845
863
|
// ★ P4 dedup(제한적 재시도·리뷰) — (layer:spec)별 시도횟수. 성공 시 영구 차단, 실패는 최대 2회까지
|
|
846
864
|
// 허용(일시 네트워크/프로세스 오류는 재시도, 지속 실패는 루프 차단).
|
|
847
865
|
const provisionAttempts = new Map<string, number>();
|
|
848
866
|
const PROVISION_MAX_ATTEMPTS = 2;
|
|
849
|
-
|
|
867
|
+
const recoverAfterMs = opts.recoverAfterMs ?? 45_000;
|
|
868
|
+
if (!Number.isFinite(recoverAfterMs) || recoverAfterMs <= 0) throw new Error('recoverAfterMs must be positive');
|
|
869
|
+
let lastScreen: string | undefined;
|
|
870
|
+
let repeatedQuestion = 0;
|
|
871
|
+
let lastQuestion: string | undefined;
|
|
872
|
+
let recoveryStage = 0;
|
|
873
|
+
let pendingReplayBlocked: string | undefined;
|
|
874
|
+
// The recovery input returned by the last decide(); committed (stage advance + event) only when the PTY accepts it.
|
|
875
|
+
let pendingRecovery: { blocked: string; action: string; text: string; replay?: boolean } | undefined;
|
|
876
|
+
let lastCommand = opts.initialCommand ?? opts.mission;
|
|
877
|
+
const llmBrain = createLlmControlBrain({
|
|
850
878
|
goal: opts.mission,
|
|
851
879
|
stream: opts.stream,
|
|
852
880
|
maxTokens: 500,
|
|
@@ -887,7 +915,10 @@ ${opts.handoffEnabled ? ` - "handoff": 다른 담당자로 넘길 때 to(codex/c
|
|
|
887
915
|
if (decision.reason && decision.action !== 'ask-human') opts.onSummary?.(decision.reason.slice(0, 2000));
|
|
888
916
|
if (decision.action === 'handoff' && decision.to && decision.mission) return { action: 'handoff', to: decision.to, mission: decision.mission, ...(decision.carry ? { carry: decision.carry } : {}) };
|
|
889
917
|
if (decision.action === 'ask-human') return { action: 'ask-human', reason: decision.reason, ...(decision.url ? { url: decision.url } : {}), ...(decision.code ? { code: decision.code } : {}) };
|
|
890
|
-
if (decision.action === 'send')
|
|
918
|
+
if (decision.action === 'send') {
|
|
919
|
+
lastCommand = decision.text || 'continue';
|
|
920
|
+
return { action: 'input', text: `${lastCommand}\r` };
|
|
921
|
+
}
|
|
891
922
|
if (decision.action === 'verify' || decision.action === 'done') return { action: 'done', reason: decision.reason || 'agent completed' };
|
|
892
923
|
if (decision.action === 'search') {
|
|
893
924
|
await opts.search(decision.query || opts.mission.slice(0, 120), obs.step);
|
|
@@ -922,6 +953,57 @@ ${opts.handoffEnabled ? ` - "handoff": 다른 담당자로 넘길 때 to(codex/c
|
|
|
922
953
|
return { action: 'wait' };
|
|
923
954
|
},
|
|
924
955
|
});
|
|
956
|
+
return {
|
|
957
|
+
onInputResult: (text, accepted) => {
|
|
958
|
+
const recovery = pendingRecovery;
|
|
959
|
+
// Only the input this brain returned settles its recovery step (a trust answer in between does not).
|
|
960
|
+
if (!recovery || recovery.text !== text) return;
|
|
961
|
+
pendingRecovery = undefined;
|
|
962
|
+
if (!accepted) return;
|
|
963
|
+
if (recovery.replay) { pendingReplayBlocked = undefined; opts.onRecover?.(recovery.blocked, recovery.action); return; }
|
|
964
|
+
recoveryStage += 1;
|
|
965
|
+
if (recovery.action === 'Ctrl-C and replay previous command') pendingReplayBlocked = recovery.blocked;
|
|
966
|
+
else opts.onRecover?.(recovery.blocked, recovery.action);
|
|
967
|
+
},
|
|
968
|
+
decide: async (obs, signal) => {
|
|
969
|
+
pendingRecovery = undefined;
|
|
970
|
+
if (signal?.aborted) return { action: 'wait' };
|
|
971
|
+
const lines = classifierFrameLines(obs.screen);
|
|
972
|
+
// Only the active bottom line can be a repeated prompt; a question left in
|
|
973
|
+
// scrollback while new work appears is not a request for input.
|
|
974
|
+
const tail = lines.at(-1);
|
|
975
|
+
const question = tail && /\?(?:\s*\[[^\]\r\n]+\])?\s*$/.test(tail) ? tail : undefined;
|
|
976
|
+
const screenChanged = lastScreen !== undefined && obs.screen !== lastScreen;
|
|
977
|
+
if (screenChanged) recoveryStage = 0;
|
|
978
|
+
repeatedQuestion = question ? (!screenChanged && question === lastQuestion ? repeatedQuestion + 1 : 1) : 0;
|
|
979
|
+
lastQuestion = question;
|
|
980
|
+
lastScreen = obs.screen;
|
|
981
|
+
if (pendingReplayBlocked !== undefined) {
|
|
982
|
+
// Ctrl-C consumes its own step; replay on the next observation even when the
|
|
983
|
+
// interrupted child keeps redrawing a non-prompt screen. A rejected replay is retried.
|
|
984
|
+
const text = `${lastCommand}\r`;
|
|
985
|
+
pendingRecovery = { blocked: pendingReplayBlocked, action: 'Ctrl-C and replay previous command', text, replay: true };
|
|
986
|
+
return { action: 'input', text };
|
|
987
|
+
}
|
|
988
|
+
const blocked = repeatedQuestion >= 3 ? `same question repeated three times: ${question}`
|
|
989
|
+
: (obs.sameScreenMs ?? 0) >= recoverAfterMs ? `screen unchanged for ${obs.sameScreenMs}ms` : undefined;
|
|
990
|
+
if (blocked && opts.evidenceReady()) return { action: 'done', reason: 'Evidence ready on stalled screen' };
|
|
991
|
+
if (blocked && !signal?.aborted) {
|
|
992
|
+
const action = ['Enter', 'Esc', 'Ctrl-C and replay previous command', 'Human needed'] as const;
|
|
993
|
+
const next = action[Math.min(recoveryStage, 3)]!;
|
|
994
|
+
if (next !== 'Human needed') {
|
|
995
|
+
const text = next === 'Enter' ? '\r' : next === 'Esc' ? '\x1b' : '\x03';
|
|
996
|
+
pendingRecovery = { blocked, action: next, text };
|
|
997
|
+
return { action: 'input', text };
|
|
998
|
+
}
|
|
999
|
+
recoveryStage += 1;
|
|
1000
|
+
opts.onRecover?.(blocked, next);
|
|
1001
|
+
const summary = lines.slice(-3).join(' | ').slice(0, 240) || '(empty screen)';
|
|
1002
|
+
return { action: 'no-progress', terminal: true, reason: `사람 필요 · 마지막 화면 요약: ${summary}` };
|
|
1003
|
+
}
|
|
1004
|
+
return llmBrain.decide(obs, signal);
|
|
1005
|
+
},
|
|
1006
|
+
};
|
|
925
1007
|
}
|
|
926
1008
|
|
|
927
1009
|
/**
|
|
@@ -1273,6 +1355,21 @@ export async function runAgentMission(spec: AgentMissionSpec, deps: AgentMission
|
|
|
1273
1355
|
} finally { signal?.removeEventListener('abort', stopWaiting); }
|
|
1274
1356
|
}
|
|
1275
1357
|
const startedAt = Date.now();
|
|
1358
|
+
const contextOwner = !deps.contextEventRun;
|
|
1359
|
+
const contextRunId = contextOwner ? ensureRunId() : undefined;
|
|
1360
|
+
let contextPrNumber: number | undefined;
|
|
1361
|
+
const emitContext = (kind: 'task-claimed' | 'task-done', detail: string): void => {
|
|
1362
|
+
if (!contextOwner) return;
|
|
1363
|
+
try {
|
|
1364
|
+
(deps.emitContextEvent ?? emitSessionEvent)({ kind, seat: 'TC', text: `Agent mission ${detail}: ${spec.mission.split(/\r?\n/, 1)[0]}`,
|
|
1365
|
+
ref: `runId=${contextRunId}${contextPrNumber === undefined ? '' : ` PR=#${contextPrNumber}`}` });
|
|
1366
|
+
} catch (error) {
|
|
1367
|
+
try { debug.log('agent-mission', 'context-event-failed', { kind, runId: contextRunId, error: String(error) }, { level: 'warn' }); } catch { /* fail-soft */ }
|
|
1368
|
+
}
|
|
1369
|
+
};
|
|
1370
|
+
emitContext('task-claimed', 'claimed');
|
|
1371
|
+
let result: AgentMissionResult | undefined;
|
|
1372
|
+
try {
|
|
1276
1373
|
const initialFiles = workdir && spec.evidence.kind === 'doc' ? initialEvidenceFiles(workdir, spec.evidence) : undefined;
|
|
1277
1374
|
let handle: PtyHandle | undefined;
|
|
1278
1375
|
let childPid: number | undefined;
|
|
@@ -1306,10 +1403,12 @@ export async function runAgentMission(spec: AgentMissionSpec, deps: AgentMission
|
|
|
1306
1403
|
});
|
|
1307
1404
|
signal?.addEventListener('abort', abort, { once: true });
|
|
1308
1405
|
if (signal?.aborted) abort();
|
|
1309
|
-
let result: AgentMissionResult;
|
|
1310
1406
|
try {
|
|
1311
1407
|
if (cancelled) await aborted;
|
|
1312
|
-
const body = runAgentMissionBody(spec, { ...deps,
|
|
1408
|
+
const body = runAgentMissionBody(spec, { ...deps, contextEventRun: true, onMissionPrNumber: (number) => {
|
|
1409
|
+
contextPrNumber = number;
|
|
1410
|
+
try { deps.onMissionPrNumber?.(number); } catch { /* context propagation never changes a mission result */ }
|
|
1411
|
+
}, onControlActive: (pending) => {
|
|
1313
1412
|
activeSettled = false;
|
|
1314
1413
|
activeControl = pending.then(() => { activeSettled = true; }, () => { activeSettled = true; });
|
|
1315
1414
|
}, onControlPending: (pending) => {
|
|
@@ -1377,10 +1476,12 @@ export async function runAgentMission(spec: AgentMissionSpec, deps: AgentMission
|
|
|
1377
1476
|
if (paths.length) {
|
|
1378
1477
|
debug.log('agent-mission', 'outside-write', { workdir, pids: [], reason: paths.join(', ') });
|
|
1379
1478
|
(deps.emitDecision ?? emitDecision)({ kind: 'ESCALATE', what: '작업 폴더 밖 쓰기', reason: paths.join(', '), purpose: '사람이 확인한다', target: 'human' });
|
|
1380
|
-
|
|
1479
|
+
result = { ...result, ok: false, reason: 'outside-write', paths, committed: false };
|
|
1480
|
+
return result;
|
|
1381
1481
|
}
|
|
1382
1482
|
}
|
|
1383
1483
|
return result;
|
|
1484
|
+
} finally { emitContext('task-done', result?.ok ? 'completed' : 'ended'); }
|
|
1384
1485
|
}
|
|
1385
1486
|
|
|
1386
1487
|
async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDeps, startedAt: number, initialFiles?: ReadonlyMap<string, string>): Promise<AgentMissionResult> {
|
|
@@ -1779,6 +1880,8 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
|
|
|
1779
1880
|
if (deps.isMissionAborted?.()) throw new Error('MISSION_ABORTED');
|
|
1780
1881
|
// 미션 전송 — 인핸싱/기억으로 가공됐으면(멀티라인) 파일로 떨궈 read 지시(TUI 멀티라인 위험·verbatim 보존).
|
|
1781
1882
|
// 원문 그대로면(단문) 타이핑.
|
|
1883
|
+
let submittedCommand = spec.mission;
|
|
1884
|
+
const submittedInputs: string[] = [];
|
|
1782
1885
|
if (missionText !== spec.mission) {
|
|
1783
1886
|
const pf = join(wt.path, '.mission-prompt.md');
|
|
1784
1887
|
try { writeFileSync(pf, missionText); } catch { /* noop */ }
|
|
@@ -1786,9 +1889,12 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
|
|
|
1786
1889
|
? '.mission-prompt.md 파일을 읽고 그 안의 미션을 완수하라. [elanous 인핸싱]의 커버리지 체크리스트 모든 항목을 산출물에 빠짐없이 반영하고(요약·일반화 금지), 완료하면 MISSION-COMPLETE 라고 답하라.'
|
|
1787
1890
|
: '.mission-prompt.md 파일을 읽고 그 안의 미션을 완수하라(원문 그대로·[elanous 기억]은 참조 컨텍스트). 완료하면 MISSION-COMPLETE 라고 답하라.';
|
|
1788
1891
|
debug.log('agent-mission', 'mission-send', { via: 'file', file: '.mission-prompt.md', chars: missionText.length, enhanced: enhanceActive });
|
|
1892
|
+
submittedCommand = note;
|
|
1893
|
+
submittedInputs.push(note);
|
|
1789
1894
|
drive(note); await sleep(800); drive('\r');
|
|
1790
1895
|
} else {
|
|
1791
1896
|
debug.log('agent-mission', 'mission-send', { via: 'type', chars: spec.mission.length });
|
|
1897
|
+
submittedInputs.push(spec.mission);
|
|
1792
1898
|
drive(spec.mission); await sleep(800); drive('\r');
|
|
1793
1899
|
}
|
|
1794
1900
|
|
|
@@ -1803,6 +1909,9 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
|
|
|
1803
1909
|
backend,
|
|
1804
1910
|
stream: deps.controlStream,
|
|
1805
1911
|
handoffEnabled: true,
|
|
1912
|
+
recoverAfterMs: spec.recoverAfterMs,
|
|
1913
|
+
initialCommand: submittedCommand,
|
|
1914
|
+
onRecover: (blocked, action) => { decisionEvent({ step: 'recover', text: action, detail: { blocked, action } }); },
|
|
1806
1915
|
onSummary: (summary) => { controllerSummary = summary; },
|
|
1807
1916
|
onDecision: (text, reply) => {
|
|
1808
1917
|
pendingAnswer = reply ? { text: `${reply.answer || 'continue'}\r`, ...reply } : undefined;
|
|
@@ -1824,6 +1933,7 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
|
|
|
1824
1933
|
// before asking the LLM — otherwise the mission text is typed into the prompt and the run stalls.
|
|
1825
1934
|
const trustAwareBrain: RunSupervisor = {
|
|
1826
1935
|
decide: (obs, signal) => {
|
|
1936
|
+
decisionEvent({ step: 'read', text: `step ${obs.step} (${obs.state}): ${classifierFrameLines(obs.screen).at(-1) ?? 'empty screen'}` });
|
|
1827
1937
|
pendingAnswer = undefined;
|
|
1828
1938
|
let answer: string | undefined;
|
|
1829
1939
|
if (backend.handleTrust?.(obs.screen, (bytes) => { answer = bytes; }) && answer !== undefined) {
|
|
@@ -1889,7 +1999,8 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
|
|
|
1889
1999
|
handoffPrSkipped = true;
|
|
1890
2000
|
debug.log('agent-mission', 'handoff-pr-skipped', { reason: 'origin-not-github' });
|
|
1891
2001
|
} else {
|
|
1892
|
-
(deps.dispatchOpenPullRequest ?? dispatchOpenPullRequest)({ title: `agent-mission: ${spec.branch}`, body: deps.originalMission ?? spec.mission, head: spec.branch, base: spec.base ?? 'main' }, { cwd: worktree });
|
|
2002
|
+
const opened = (deps.dispatchOpenPullRequest ?? dispatchOpenPullRequest)({ title: `agent-mission: ${spec.branch}`, body: deps.originalMission ?? spec.mission, head: spec.branch, base: spec.base ?? 'main' }, { cwd: worktree });
|
|
2003
|
+
if (opened.number !== null) deps.onMissionPrNumber?.(opened.number);
|
|
1893
2004
|
}
|
|
1894
2005
|
gatedHandoff = true;
|
|
1895
2006
|
},
|
|
@@ -1916,7 +2027,6 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
|
|
|
1916
2027
|
renderPng: () => h.renderScreenPng(),
|
|
1917
2028
|
bus,
|
|
1918
2029
|
ident: { ptyId: h.id, instance: resolveInstanceName(), runId },
|
|
1919
|
-
onPublishedFrame: (obs) => decisionEvent({ step: 'read', text: `step ${obs.step} (${obs.state}): ${classifierFrameLines(obs.screen).at(-1) ?? 'empty screen'}` }),
|
|
1920
2030
|
});
|
|
1921
2031
|
debug.log('agent-mission', 'takeover-wait', { id: h.id, maxWaitMs: AGENT_MISSION_TAKEOVER_WAIT_MS });
|
|
1922
2032
|
const controlRun = runWithControlObserve(
|
|
@@ -1932,7 +2042,12 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
|
|
|
1932
2042
|
pendingAnswer = undefined;
|
|
1933
2043
|
const accepted = inject(text);
|
|
1934
2044
|
if (accepted) {
|
|
2045
|
+
submittedInputs.push(text);
|
|
1935
2046
|
decisionEvent({ step: 'input', text: `Sent ${text.startsWith('\x1b') ? 'key' : 'text'}: ${text.startsWith('\x1b') ? 'navigation' : text}` });
|
|
2047
|
+
}
|
|
2048
|
+
// Recovery stages and recover events are committed only after the PTY accepted the input.
|
|
2049
|
+
brain.onInputResult(text, accepted);
|
|
2050
|
+
if (accepted) {
|
|
1936
2051
|
if (reply) decisionEvent({ step: 'answer', text: 'Answered agent question', detail: { question: reply.question, answer: reply.answer } });
|
|
1937
2052
|
}
|
|
1938
2053
|
return accepted;
|
|
@@ -1948,6 +2063,8 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
|
|
|
1948
2063
|
} : {}),
|
|
1949
2064
|
awaitOwnership: { maxWaitMs: AGENT_MISSION_TAKEOVER_WAIT_MS },
|
|
1950
2065
|
settle: async () => {
|
|
2066
|
+
// A frozen screen goes quiet after 6 s, so stall recovery is still observed; work that keeps
|
|
2067
|
+
// drawing output keeps the original 360 s settle and round budget.
|
|
1951
2068
|
const quiet = waitForQuiet(h, 6000, 360000);
|
|
1952
2069
|
await (deps.awaitMission ? deps.awaitMission(quiet) : quiet);
|
|
1953
2070
|
},
|
|
@@ -1986,7 +2103,7 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
|
|
|
1986
2103
|
throw error;
|
|
1987
2104
|
}
|
|
1988
2105
|
},
|
|
1989
|
-
}, { maxSteps: maxRounds });
|
|
2106
|
+
}, { maxSteps: maxRounds, stuckLimit: maxRounds + 1 });
|
|
1990
2107
|
deps.onControlActive?.(pending);
|
|
1991
2108
|
return pending;
|
|
1992
2109
|
},
|
|
@@ -2079,24 +2196,29 @@ async function runAgentMissionBody(spec: AgentMissionSpec, deps: AgentMissionDep
|
|
|
2079
2196
|
if (deps.isMissionAborted?.()) return { ok: false, reason: 'aborted', worktree: wt.path, branch: wt.branch,
|
|
2080
2197
|
rounds: round, evidencePath: null, committed: false, usedOmniCrawl: usedOmni, detail: 'aborted' };
|
|
2081
2198
|
const finalEv = verifyEvidence(wt.path, spec.evidence);
|
|
2082
|
-
const
|
|
2199
|
+
const finalScreen = await h.renderScreen();
|
|
2200
|
+
const driveVerdict = verdictForFinalPtyScreen({ screen: finalScreen, inputHistory: submittedInputs, exitCode: h.exitCode ?? null });
|
|
2201
|
+
debug.log('agent-mission', 'drive-verdict', { kind: driveVerdict.kind, reason: driveVerdict.reason.slice(0, 200) });
|
|
2202
|
+
const recoverExhausted = control.termination.kind === 'stuck' && control.termination.reason.startsWith('사람 필요');
|
|
2203
|
+
const accepted = finalEv.ok && !recoverExhausted && driveVerdict.kind !== 'done-but-failed';
|
|
2204
|
+
const evidencePath = accepted ? finalEv.path : null;
|
|
2083
2205
|
let committed = false;
|
|
2084
|
-
if (!deps.isMissionAborted?.() && !spec.workdir &&
|
|
2206
|
+
if (!deps.isMissionAborted?.() && !spec.workdir && accepted && (spec.commit ?? true)) {
|
|
2085
2207
|
const c = (deps.commitWorktree ?? commitWorktree)(wt.path, `chore(agent-mission): ${spec.branch} — ${backend.name}-in-elanous PTY RFC 산출`);
|
|
2086
2208
|
committed = c.ok;
|
|
2087
2209
|
debug.log('agent-mission', 'commit', { ok: c.ok, out: c.out.slice(0, 120) });
|
|
2088
2210
|
}
|
|
2089
|
-
debug.log('agent-mission', 'result', { ok:
|
|
2090
|
-
decisionEvent({ step: 'done', text:
|
|
2091
|
-
stopLive(
|
|
2211
|
+
debug.log('agent-mission', 'result', { ok: accepted, rounds: round, evidencePath, committed, usedOmni });
|
|
2212
|
+
decisionEvent({ step: 'done', text: accepted ? 'Mission evidence accepted' : 'Mission ended without sufficient evidence', detail: { result: { kind: evidencePath ? 'file' : 'text', ref: evidencePath ?? (control.termination.kind === 'error' ? control.termination.message : recoverExhausted ? control.termination.reason : 'evidence unavailable') } } });
|
|
2213
|
+
stopLive(accepted);
|
|
2092
2214
|
if (h.pid) await terminateMissionProcessTree(h.pid);
|
|
2093
2215
|
try { h.kill(); } catch { /* noop */ }
|
|
2094
2216
|
|
|
2095
2217
|
return {
|
|
2096
|
-
ok:
|
|
2218
|
+
ok: accepted, worktree: wt.path, branch: wt.branch, rounds: round,
|
|
2097
2219
|
evidencePath, committed, usedOmniCrawl: usedOmni,
|
|
2098
2220
|
ptyId: h.id, webUrl: webAddress.webUrl,
|
|
2099
|
-
detail: done ? '완료(증거 충족)' : (finalEv.ok ? '증거 충족(루프 종료)' : '미완(증거 부족)'),
|
|
2221
|
+
detail: recoverExhausted ? control.termination.reason : driveVerdict.kind === 'done-but-failed' ? `미완(DRIVE-OK: ${driveVerdict.reason})` : done ? '완료(증거 충족)' : (finalEv.ok ? '증거 충족(루프 종료)' : '미완(증거 부족)'),
|
|
2100
2222
|
};
|
|
2101
2223
|
} finally { stopLive(); finishContext(false); }
|
|
2102
2224
|
}
|
|
@@ -41,7 +41,7 @@ function safe(value: string): string {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/** The only emission point for the inside-PTY decision stream. Never log unsanitized input. */
|
|
44
|
-
export function emitPtyDecision(input: DecisionInput, log: typeof debug.log = debug.log): PtyDecision {
|
|
44
|
+
export function emitPtyDecision(input: DecisionInput, log: typeof debug.log = (category, event, data) => debug.log(category, event, data)): PtyDecision {
|
|
45
45
|
let stream = missionStreams.get(input.missionId);
|
|
46
46
|
if (!stream) {
|
|
47
47
|
stream = { alias: `mission-${missionStreams.size + 1}`, seq: -1 };
|
|
@@ -88,7 +88,7 @@ export type ControlDecision =
|
|
|
88
88
|
| { readonly action: 'handoff'; readonly to: 'codex' | 'claude' | 'elanous'; readonly mission: string; readonly carry?: 'diff' | 'summary' }
|
|
89
89
|
| { readonly action: 'ask-human'; readonly reason: string; readonly url?: string; readonly code?: string }
|
|
90
90
|
/** `no-progress` reports observed lack of progress without claiming completion. */
|
|
91
|
-
| { readonly action: 'no-progress'; readonly reason: string };
|
|
91
|
+
| { readonly action: 'no-progress'; readonly reason: string; readonly terminal?: boolean };
|
|
92
92
|
|
|
93
93
|
export interface RunSupervisor {
|
|
94
94
|
/** run supervisor의 관측 → 다음 행동. 동기/비동기 모두 허용(LLM 은 async). `signal` 이 abort 되면(사람 takeover
|
|
@@ -664,6 +664,9 @@ export async function runPtyControlLoop(
|
|
|
664
664
|
previousIntervention = { ...intervention, sameScreenCount: 1 };
|
|
665
665
|
}
|
|
666
666
|
} else if (decision.action === 'wait' || decision.action === 'no-progress') {
|
|
667
|
+
if (decision.action === 'no-progress' && decision.terminal) {
|
|
668
|
+
return finish({ kind: 'stuck', iteration: step, reason: decision.reason }, step, intervention, true);
|
|
669
|
+
}
|
|
667
670
|
// wait/no-progress — 종료된 자식은 새 출력을 낼 수 없으므로 대기 없이 즉시 수렴한다.
|
|
668
671
|
if (deps.isAlive?.() === false) {
|
|
669
672
|
debug.log('autopilot.control', 'child-exited', { step });
|
package/src/chat/index.ts
CHANGED
|
@@ -438,6 +438,7 @@ export const SLASH_COMMANDS: SlashCommand[] = [
|
|
|
438
438
|
{ name: 'clear', aliases: ['cls'], description: '대화 기록 지우기' },
|
|
439
439
|
{ name: 'status', aliases: ['st'], description: '지금 상태 — 모델 · 연결 · 계정' },
|
|
440
440
|
{ name: 'now', aliases: [], description: '현재 맥락과 출처 표 — /now [주제]' },
|
|
441
|
+
{ name: 'wish', aliases: [], description: '소원을 카드로 남기기 — /wish <소원 한 줄>' },
|
|
441
442
|
{ name: 'remaining', aliases: [], description: '계정별 남은 사용량' },
|
|
442
443
|
{ name: 'setup', aliases: [], description: '상세 설정 — /setup · /setup reset(다음 실행 때 마법사 다시)', subcommands: ['help', 'reset'] },
|
|
443
444
|
{ name: 'quit', aliases: ['q', 'exit'], description: '나가기' },
|
package/src/cli/persona-cli.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { parse as parseYaml } from 'yaml';
|
|
|
7
7
|
import { debug } from '../debug/log.js';
|
|
8
8
|
import { parsePersonaYaml } from '../persona/loader.js';
|
|
9
9
|
import { resolveRepositoryPersonaDir, resolveStatePersonaDir } from '../persona/global-registry.js';
|
|
10
|
-
import { choosePersonaId, clonePreset, describePreset, editPersona, findPreset, loadPresetIndex, loadPresets, presetToProfileYaml } from '../persona/presets.js';
|
|
10
|
+
import { UnknownPersonaSchemaKeyError, choosePersonaId, clonePreset, describePreset, editPersona, findPreset, loadPresetIndex, loadPresets, presetToProfileYaml } from '../persona/presets.js';
|
|
11
11
|
import { addTeamSet, listTeamSets, removeTeamSet } from '../persona/team-set.js';
|
|
12
12
|
|
|
13
13
|
interface StoredPersona { personaId: string; displayName: string; description?: string; preset?: string; title?: string; path: string }
|
|
@@ -43,11 +43,11 @@ function mapping(value: unknown): value is Record<string, unknown> {
|
|
|
43
43
|
|
|
44
44
|
function setEdit(edits: Record<string, unknown>, assignment: string): void {
|
|
45
45
|
const equals = assignment.indexOf('=');
|
|
46
|
-
if (equals <
|
|
46
|
+
if (equals < 0) throw new Error(`--set requires key=value: ${assignment}`);
|
|
47
47
|
const key = assignment.slice(0, equals);
|
|
48
48
|
const parts = key.split('.');
|
|
49
49
|
if (parts.some((part) => !part || ['__proto__', 'prototype', 'constructor'].includes(part))) {
|
|
50
|
-
throw new
|
|
50
|
+
throw new UnknownPersonaSchemaKeyError(key);
|
|
51
51
|
}
|
|
52
52
|
const value: unknown = parseYaml(assignment.slice(equals + 1));
|
|
53
53
|
let target = edits;
|
|
@@ -94,7 +94,7 @@ export function registerPersonaCommands(program: Command): void {
|
|
|
94
94
|
if (opts.json) { console.log(JSON.stringify({ presets: presets.map((p) => ({ id: p.personaId, displayName: p.displayName, role: p.role, oneLine: p.oneLine })), mine }, null, 2)); return; }
|
|
95
95
|
if (!presets.length) console.log('페르소나 프리셋을 찾지 못했습니다(persona-presets/ 가 이 설치본에 없습니다).');
|
|
96
96
|
else {
|
|
97
|
-
console.log('프리셋 — 골라서 내 페르소나로 만들기: elanous persona add
|
|
97
|
+
console.log('프리셋 — 골라서 내 페르소나로 만들기: elanous persona add <이름> [--from <프리셋>]');
|
|
98
98
|
const width = Math.max(...presets.map((p) => p.personaId.length));
|
|
99
99
|
for (const p of presets) console.log(` ${p.personaId.padEnd(width)} ${p.displayName} — ${p.role} · ${p.oneLine}`);
|
|
100
100
|
}
|
|
@@ -105,24 +105,27 @@ export function registerPersonaCommands(program: Command): void {
|
|
|
105
105
|
}
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
-
persona.command('add <
|
|
109
|
-
.description('Make your own persona from a preset
|
|
108
|
+
persona.command('add <name>')
|
|
109
|
+
.description('Make your own persona from a preset — the preset file is not changed')
|
|
110
|
+
.option('--from <preset>', 'Preset to clone (defaults to the named preset)')
|
|
110
111
|
.option('--name <name>', 'What to call it (default: the preset’s first name)')
|
|
111
112
|
.option('--as <name>', 'Clone this preset under a new, unique name')
|
|
112
113
|
.option('--title <title>', 'Optional title, e.g. CMO')
|
|
113
114
|
.option('--json', 'JSON output')
|
|
114
|
-
.action((query: string, opts: { name?: string; as?: string; title?: string; json?: boolean }) => {
|
|
115
|
+
.action((query: string, opts: { from?: string; name?: string; as?: string; title?: string; json?: boolean }) => {
|
|
115
116
|
const presets = loadPresets();
|
|
116
|
-
const
|
|
117
|
+
const presetQuery = opts.from ?? query;
|
|
118
|
+
const preset = findPreset(presets, presetQuery);
|
|
117
119
|
if (!preset) {
|
|
118
|
-
console.error(`프리셋 «${
|
|
120
|
+
console.error(`프리셋 «${presetQuery}» 을 찾지 못했습니다. 있는 것: ${presets.map((p) => p.personaId).join(', ')}`);
|
|
119
121
|
process.exitCode = 1;
|
|
120
122
|
return;
|
|
121
123
|
}
|
|
122
|
-
if (opts.as !== undefined) {
|
|
124
|
+
if (opts.as !== undefined || opts.from !== undefined) {
|
|
123
125
|
try {
|
|
124
|
-
if (opts.name !== undefined || opts.title !== undefined) throw new Error('--
|
|
125
|
-
|
|
126
|
+
if (opts.from !== undefined && (opts.as !== undefined || opts.name !== undefined || opts.title !== undefined)) throw new Error('--from cannot be combined with --as, --name or --title');
|
|
127
|
+
if (opts.as !== undefined && (opts.name !== undefined || opts.title !== undefined)) throw new Error('--as cannot be combined with --name or --title');
|
|
128
|
+
const result = clonePreset(preset, opts.from !== undefined ? query : opts.as!);
|
|
126
129
|
debug.log('persona.cli', 'cloned', { preset: preset.personaId, personaId: result.personaId });
|
|
127
130
|
if (opts.json) console.log(JSON.stringify({ personaId: result.personaId, displayName: result.displayName, preset: preset.personaId, path: result.path }, null, 2));
|
|
128
131
|
else {
|
|
@@ -170,8 +173,8 @@ export function registerPersonaCommands(program: Command): void {
|
|
|
170
173
|
console.log(` 파일: ${result.path}`);
|
|
171
174
|
}
|
|
172
175
|
} catch (err) {
|
|
173
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
174
|
-
process.exitCode = 1;
|
|
176
|
+
console.error(err instanceof Error ? err.message.replace(/\s+/g, ' ').trim() : String(err));
|
|
177
|
+
process.exitCode = err instanceof UnknownPersonaSchemaKeyError ? 2 : 1;
|
|
175
178
|
}
|
|
176
179
|
});
|
|
177
180
|
|
package/src/cli/pod-cli.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import type { Command } from 'commander';
|
|
5
5
|
import { debug } from '../debug/log.js';
|
|
6
|
+
import { readPodMemoryAdvice } from './pod-memory-advice.js';
|
|
6
7
|
import { getUserConfig } from '../user-config.js';
|
|
7
8
|
import { listCodexAccountsInStore } from '../oauth/codex-account-store.js';
|
|
8
9
|
import { parsePodPool, resolvePodPoolSpec, type PoolKubectl } from '../task-orchestrator/surfaces/pod-pool.js';
|
|
@@ -24,6 +25,7 @@ export interface PodCliDeps {
|
|
|
24
25
|
perAccount?: () => number;
|
|
25
26
|
poolSpec?: (explicit?: string) => string | null;
|
|
26
27
|
harnessPool?: () => string | undefined;
|
|
28
|
+
memoryAdvice?: (logsDb?: string) => ReturnType<typeof readPodMemoryAdvice>;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
const HELP = `elanous pod run [--pool <스펙>] [--skill <이름,…>] [--llm grok] [--clone] [--deadline <초>] -- <명령…>
|
|
@@ -38,6 +40,21 @@ export function registerPodCommands(program: Command, deps: PodCliDeps = {}): vo
|
|
|
38
40
|
};
|
|
39
41
|
const run = deps.run ?? ((options: RunPodCommandOptions) => runPodCommand(options));
|
|
40
42
|
const pod = program.command('pod').description('Pod 명령 Job — 하니스 없이 명령 하나를 돌리고 산출을 돌려받는다');
|
|
43
|
+
pod.command('memory').description('실측 기반 Pod 메모리 권고 (읽기 전용)')
|
|
44
|
+
.command('advise').description('골 종류별 standard 16Gi / high 32Gi 권고와 근거')
|
|
45
|
+
.option('--json', '권고와 측정 근거를 JSON 으로')
|
|
46
|
+
.option('--logs-db <path>', '기존 logs.db 경로')
|
|
47
|
+
.action((opts: { json?: boolean; logsDb?: string }) => {
|
|
48
|
+
try {
|
|
49
|
+
const advice = (deps.memoryAdvice ?? readPodMemoryAdvice)(opts.logsDb);
|
|
50
|
+
if (opts.json) io.log(JSON.stringify(advice));
|
|
51
|
+
else for (const row of advice.byGoalType) io.log(`${row.goalType}: ${row.recommended} ${row.limit} · ${row.reason}`);
|
|
52
|
+
io.exit(0);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
io.error(err instanceof Error ? err.message : String(err));
|
|
55
|
+
io.exit(1);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
41
58
|
pod.command('lease').description('Pod 풀 동시 실행 권장 (DNS 점검용 Pod 를 잠깐 만들었다 지움)')
|
|
42
59
|
.command('status').description('지금 추가로 쏠 수 있는 Pod 골과 제한 근거')
|
|
43
60
|
.option('--pool <spec>', '풀 스펙 — 컨텍스트[@ssh호스트][:상한][#레지스트리:포트] 쉼표')
|