elanous 0.2.16 → 0.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/{1307-b46d45b705e8c9ae.js → 1307-aac7332576c4d6fc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{6877-7c6208705c284124.js → 6877-fe94e76a5841ee9b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8401-4ce1c42c8d89903f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/899-f41be85e8a680651.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9247-269e4464cc4b491c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-3143b07a817d1da9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-3cb52e5ea8fc2fa5.js → page-17ba086ef82d339b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-7eb661ff23af7da2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-6b8444914da74ca6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-482c5dbde699947c.js → page-6e5c3a867557298b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/{page-1e962b31d1f11081.js → page-7110f732f45bf944.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-8c2d9526164f5d09.js → page-9a41d65355fc25b9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-5e77cd1802567c89.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-41afec0b255ed2bb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-c53598cfce6156f2.js → page-4187666132fc98c4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-13a63953f53c6a23.js → page-09328d499162bae4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-5d3c8429d04f3831.js → page-29ff3a8f84f03b6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-3193bdc0bbab047a.js → page-80ec7401bc83f511.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/{page-76270efaee638f17.js → page-ab93b240f687d69a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-6107ca8ddd5e3bdd.js → page-fc7d86467291ae38.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{layout-dce686e582c23937.js → layout-de43a186e5ba5683.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/{page-d3d53b146416e422.js → page-8b08cafad27759c9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-0e2d6985fb42cd2f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-13e4108999f58d13.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-e8296ee284aa1293.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-4167348049166852.js → page-043a9111e74f6b24.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-de52e8fdf9095f7a.js → not-found-23a4601ceb7f452e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-1ed1324b1dc36754.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-ca3952cd78a83ca2.js → page-10d958605a77b3bd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-5b042736aa113756.js → page-166745e51ac0c7b0.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-acb749f1e432bcf3.js → page-a04260b8ccc968d4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-7a1d85e121065cc8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-1f50e3d4e13435f4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-1ebfe049c640e171.js → page-12916918988ac97e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-d8a3347a9d0c3f4a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-a9117d2cd3093d36.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-31fa1ce4c4aff9ae.js → page-5a64cb5208c6a8fd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-0f5cd57ca928f42b.js → page-1407ec83ea64c1f9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-abe8e3b74bea1ffc.js → page-1d824510df025d18.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-6e878ebbe1737d00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/{page-d28573d6792b4275.js → page-7f49fe645d5132f7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-59f03fda76c03d22.js → page-81a4e7f8dbfe74cb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-d751de5f8062ff5e.js → page-27d71f131acd9320.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-3ab59d19913b200e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-bf85b2f5bf077925.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-424659bacfb8cdd7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-ab32a8f522a60371.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-d4aef67abf264874.js → page-fc2df261f8a2c7df.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-1ea64acadd704ace.js → page-09461366af82ef13.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-3544f154c87b43ee.js → page-5d4fef9ccd7d406b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-30c2073bfc21c004.js → page-ea90a8ac73ce2749.js} +1 -1
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +13 -13
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +13 -13
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +13 -13
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +13 -13
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +13 -13
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +13 -13
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +13 -13
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +13 -13
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +13 -13
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +13 -13
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +13 -13
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +13 -13
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +13 -13
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +13 -13
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +13 -13
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +13 -13
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +13 -13
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +13 -13
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +13 -13
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +13 -13
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +13 -13
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +13 -13
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +13 -13
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +13 -13
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +13 -13
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +13 -13
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +14 -14
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +13 -13
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +13 -13
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +13 -13
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +13 -13
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +13 -13
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +13 -13
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +13 -13
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +13 -13
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +13 -13
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +13 -13
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +13 -13
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +13 -13
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +13 -13
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +13 -13
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +13 -13
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +13 -13
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +13 -13
- package/graphs/intake/intake-daily.yaml +9 -2
- package/graphs/intake/news-sources.yaml +4 -0
- package/graphs/intake/recipes.yaml +6 -1
- package/package.json +1 -1
- package/scripts/__fixtures__/intake-news.xml +7 -0
- package/scripts/intake-news.ts +169 -0
- package/scripts/release-loop/docs-land-node.ts +69 -10
- package/scripts/release-loop/publish-node.ts +2 -2
- package/scripts/release-loop/release-docs.ts +134 -0
- package/scripts/release-loop/version-node.ts +7 -4
- package/scripts/release-story/draft.ts +42 -14
- package/src/agent-mission/driver.ts +53 -10
- package/src/autopilot/track-agent.ts +5 -3
- package/src/cli/autopilot-cli.ts +66 -0
- package/src/cli/decisions-cli.ts +4 -1
- package/src/cli/intake-cli.ts +1 -1
- package/src/cli/log-event-names.ts +1 -0
- package/src/cli/pr-cli.ts +12 -1
- package/src/cli/provider-cli.ts +14 -0
- package/src/cli/self-update-canary.ts +46 -0
- package/src/cli/self-update.ts +200 -1
- package/src/context-card/index.ts +45 -0
- package/src/context-card/task.ts +76 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +2 -0
- package/src/decisions/decision-ledger.ts +10 -1
- package/src/harness/harness-cli-command.ts +142 -10
- package/src/harness/harness-pod-dispatch.ts +11 -0
- package/src/harness/harness-queue.ts +86 -7
- package/src/harness/nested-elanous-policy.ts +23 -4
- package/src/hq/fence-wrapper.ts +123 -0
- package/src/hq/hq.ts +8 -0
- package/src/index.ts +57 -8
- package/src/intake-plane/digest.ts +39 -4
- package/src/nexus/api/grid.ts +61 -0
- package/src/nexus/api/http-server.ts +19 -1
- package/src/nexus/api/task-cards-api.ts +19 -0
- package/src/oauth/codex.ts +82 -2
- package/src/release-loop/landing-freeze.ts +18 -10
- package/src/seat-loop/seat-loop.ts +95 -31
- package/src/self-dev/ask-launch-flow.ts +26 -7
- package/src/self-dev/run-store.ts +5 -1
- package/src/self-dev/run-supervisor.ts +7 -1
- package/src/self-dev/supervisor-verdict-edges.ts +46 -0
- package/src/self-implement/frozen-merges.ts +25 -20
- package/src/self-implement/goal-author-clarification.ts +81 -0
- package/src/self-implement/goal-author-cli.ts +33 -5
- package/src/self-implement/goal-author.ts +88 -12
- package/src/self-implement/goal-clarification-escalation.ts +37 -1
- package/src/self-implement/goal-document-write.ts +28 -0
- package/src/self-implement/heal-intake.ts +4 -0
- package/src/self-implement/host-regate.ts +3 -1
- package/src/self-implement/orchestrator.ts +19 -1
- package/src/task-agent/actions.ts +118 -0
- package/src/task-agent/judge.ts +96 -0
- package/src/task-cards/card-store.ts +4 -1
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +33 -4
- package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
- package/src/task-orchestrator/surfaces/pod-pool.ts +81 -13
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +151 -29
- package/src/telegram-commands.ts +1 -1
- package/src/telegram-loops-command.ts +21 -14
- package/src/user-config.ts +25 -3
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9247-29d889b809288e65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-79c3ed84baaedd03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-a424ef80885d1743.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-c1e20d7d338713f1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-ffff6d819fbc6e0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-a0e2a6d1bfb52fc5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-1a61c7ca1ceb3ae7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-51f811fa119308b6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-dacff46049e1557e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-36a7d994f4ca0299.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-cd78862707999911.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-54b9ef8afe6278fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-57924393e3e4e539.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-9e0ce6c8129fa641.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-4364ef9f4e7c7f0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-fe1759f94003b32b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-d64c984e855c8c68.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-86914b0dee8ab7f8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-9afdee66b820c76e.js +0 -1
- /package/apps/pwa/out/_next/static/{rv9cierV0jSOvDBm1g0XB → yRA5ur6DcPl0YHSRheH2O}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{rv9cierV0jSOvDBm1g0XB → yRA5ur6DcPl0YHSRheH2O}/_ssgManifest.js +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
4
|
+
import { effectiveInstanceRoot, prodInstanceRoot } from '../instance/resolve.js';
|
|
5
5
|
|
|
6
6
|
export interface LandingFreeze {
|
|
7
7
|
reason: string;
|
|
@@ -60,15 +60,23 @@ function mergesDir(root: string): string { return join(root, 'landing-merges');
|
|
|
60
60
|
|
|
61
61
|
export interface LandingMergeGuard { frozen: LandingFreeze | null; end: () => void }
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
/** Mark both authorities before reading either freeze: the host and the child each drain their own in-flight merges. */
|
|
64
|
+
export function beginLandingMerge(root = effectiveInstanceRoot(), now = new Date(), prodFreezeRoot = prodInstanceRoot(), forceReason?: string): LandingMergeGuard {
|
|
65
|
+
const markers: string[] = [];
|
|
66
|
+
const end = () => { for (const marker of markers) rmSync(marker, { force: true }); };
|
|
67
|
+
try {
|
|
68
|
+
for (const authority of new Set([prodFreezeRoot, root])) {
|
|
69
|
+
const dir = mergesDir(authority);
|
|
70
|
+
mkdirSync(dir, { recursive: true });
|
|
71
|
+
const marker = join(dir, `${process.pid}-${randomUUID()}.json`);
|
|
72
|
+
markers.push(marker);
|
|
73
|
+
writeFileSync(marker, `${JSON.stringify({ pid: process.pid, startedAt: now.toISOString() })}\n`, { mode: 0o600 });
|
|
74
|
+
}
|
|
75
|
+
} catch (error) { end(); throw error; }
|
|
69
76
|
let frozen: LandingFreeze | null;
|
|
70
|
-
try { frozen = readLandingFreeze(
|
|
71
|
-
|
|
77
|
+
try { frozen = readLandingFreeze(prodFreezeRoot, now) ?? (root === prodFreezeRoot ? null : readLandingFreeze(root, now)); }
|
|
78
|
+
catch (error) { end(); throw error; }
|
|
79
|
+
if (frozen && !forceReason?.trim()) { end(); return { frozen, end: () => {} }; }
|
|
72
80
|
return { frozen: null, end };
|
|
73
81
|
}
|
|
74
82
|
|
|
@@ -10,7 +10,7 @@ import { surfaceEventsDbPath } from '../domains/surface-events.js';
|
|
|
10
10
|
import { judgeNeighbor } from '../loops/neighbors.js';
|
|
11
11
|
import { addHarnessQueue, harnessQueueIdForKey, harnessQueueOutcome, listHarnessQueue } from '../harness/harness-queue.js';
|
|
12
12
|
import { isRescueAllowedAction } from '../steward/rescue.js';
|
|
13
|
-
import { DecisionLedger, type DecisionEntry } from '../decisions/decision-ledger.js';
|
|
13
|
+
import { DecisionLedger, type DecisionEntry, type DecisionOption, type Recommendation } from '../decisions/decision-ledger.js';
|
|
14
14
|
import { effectiveInstanceRoot, releaseLedgerRoot } from '../instance/resolve.js';
|
|
15
15
|
import { loopEvent } from '../loops/observe.js';
|
|
16
16
|
import { getUserConfig, type PersonaLoopConfig, type SeatLoopConfig, type UserConfig } from '../user-config.js';
|
|
@@ -43,7 +43,7 @@ export type TcCandidate =
|
|
|
43
43
|
| { kind: 'publication-pr-approval-wait'; pr: number; title: string; readyAt: string; approvalWaitAt: string; paths: string[] }
|
|
44
44
|
| { kind: 'other-seat-cell-defect'; version: string; id: string; title: string; to: SeatId };
|
|
45
45
|
export type TcPullRequest = { number: number; title: string; body?: string; state: 'OPEN' | 'MERGED' | 'CLOSED'; isDraft: boolean; createdAt: string; updatedAt?: string; mergedAt?: string | null; readyAt?: string; approvalWaitAt?: string; paths?: string[]; reviewDecision?: string };
|
|
46
|
-
export type SeatEntry = { seat: string; ts?: string; at: string; modeDowngradeReason?: string; status: 'shadow' | 'attempting' | 'outcome-unknown' | 'queued' | 'refused' | 'held' | 'launched' | 'hitl' | 'wait' | 'asked' | 'answered' | 'awaiting-answer' | 'awaiting-xcheck' | 'awaiting-resolution' | 'resolved-by-neighbor' | 'rejected-no-evidence' | 'skipped-budget' | 'skipped-empty'; item?: SeatItem; candidate?: OpCandidate | TcCandidate; action?: SeatAction; reason?: string; runId?: string; queueId?: string; inquiry?: SeatInquiry; answer?: string; escalated?: boolean; xcheckRequestedAt?: string; xcheckQuestionId?: number; xcheckNeighbor?: SeatId; xcheckNote?: string; xcheckJudgmentEvidence?: string; xcheckWaitMinutes?: number };
|
|
46
|
+
export type SeatEntry = { seat: string; ts?: string; at: string; modeDowngradeReason?: string; status: 'shadow' | 'attempting' | 'outcome-unknown' | 'queued' | 'refused' | 'held' | 'launched' | 'hitl' | 'wait' | 'asked' | 'answered' | 'awaiting-answer' | 'awaiting-xcheck' | 'awaiting-resolution' | 'resolved-by-neighbor' | 'resolved' | 'rejected-no-evidence' | 'skipped-budget' | 'skipped-empty'; item?: SeatItem; candidate?: OpCandidate | TcCandidate; action?: SeatAction; reason?: string; runId?: string; queueId?: string; inquiry?: SeatInquiry; answer?: string; escalated?: boolean; xcheckRequestedAt?: string; xcheckQuestionId?: number; xcheckNeighbor?: SeatId; xcheckNote?: string; xcheckJudgmentEvidence?: string; xcheckWaitMinutes?: number };
|
|
47
47
|
export type SeatLoopResult = SeatEntry | { seat: string; status: 'skipped-off'; modeDowngradeReason?: string };
|
|
48
48
|
|
|
49
49
|
export function seatLoopTickLine(result: SeatLoopResult): string {
|
|
@@ -375,6 +375,23 @@ function decisionSummary(text: string): string {
|
|
|
375
375
|
return Array.from(sentences).slice(0, 240).join('').trim();
|
|
376
376
|
}
|
|
377
377
|
|
|
378
|
+
function decisionCardMaterial(input: {
|
|
379
|
+
what: string; soFar: string; current: string; question: string; answer: string;
|
|
380
|
+
options: DecisionOption[]; recommendation: Recommendation; ifUntouched: string; evidence: string; confidence: string;
|
|
381
|
+
}): { scqa: { s: string; c: string; q: string; a: string }; pendingQuestion: string } {
|
|
382
|
+
const short = (value: string) => decisionSummary(value) || '관측되지 않음';
|
|
383
|
+
return {
|
|
384
|
+
scqa: { s: short(input.what), c: short(input.current), q: short(input.question), a: short(input.answer) },
|
|
385
|
+
pendingQuestion: [
|
|
386
|
+
`무엇: ${input.what}`, `지금까지: ${input.soFar}`, `지금 상태 재측: ${input.current}`,
|
|
387
|
+
...input.options.map((option) => `선택지 ${option.key} (${option.label}) 결과: ${option.consequence}`),
|
|
388
|
+
'skipped' in input.recommendation ? `권고 비움: ${input.recommendation.reason}` : `권고: ${input.recommendation.option} — ${input.recommendation.why}`,
|
|
389
|
+
`확신: ${input.confidence}`, `그냥 두면: ${input.ifUntouched}`, `근거: ${input.evidence}`,
|
|
390
|
+
`Q: ${input.question}`, `A: ${input.answer}`,
|
|
391
|
+
].join('\n'),
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
378
395
|
function decisionSourceRef(seat: string, item: SeatItem): string {
|
|
379
396
|
return item.source === 'request' && item.id.startsWith('coord:') ? item.id : decisionRef(seat, item);
|
|
380
397
|
}
|
|
@@ -388,7 +405,7 @@ function decisionReceipt(root: string, seat: string, item: SeatItem): DecisionEn
|
|
|
388
405
|
function handledKeys(ledger: readonly SeatEntry[], shadow: boolean, root?: string, liveSafe = false): Set<string> {
|
|
389
406
|
const inquiryRow = (entry: SeatEntry) => entry.action === 'seat-question' || entry.action === 'seat-answer' || !!entry.inquiry;
|
|
390
407
|
return new Set(ledger.filter((entry, index) => entry.status === 'asked' || entry.status === 'answered'
|
|
391
|
-
|| entry.status === 'resolved-by-neighbor'
|
|
408
|
+
|| entry.status === 'resolved-by-neighbor' || entry.status === 'resolved'
|
|
392
409
|
// RM3a: only TC's other-seat defect ask-shadows are deduplicated here — an ordinary inquiry recorded in shadow must still be delivered once questions turn on.
|
|
393
410
|
|| (entry.status === 'shadow' && entry.candidate?.kind === 'other-seat-cell-defect')
|
|
394
411
|
|| (entry.status === 'hitl' && (!entry.inquiry || (!!root && !!entry.item && !!decisionReceipt(root, entry.seat, entry.item))))
|
|
@@ -853,7 +870,7 @@ const SEAT_ACTION_RULES: ReadonlyArray<{ kind: 'wait' | 'harness' | 'decision';
|
|
|
853
870
|
{ kind: 'wait', signal: /실측\s*대기|(?:사람|실제)\s*기기(?!\s*(?:(?:실측|측정|확인)\s*)?완료)(?:\s*(?:실측|측정|확인|대기))?|실물\s*측정(?!\s*완료)/iu, source: 'content', resolvedBy: /(?:실측|측정|(?:사람|실제)\s*기기|실물\s*측정)\s*완료/iu },
|
|
854
871
|
{ kind: 'wait', signal: /\u{1F451}\s*확인\s*대기/iu, source: 'content', resolvedBy: /\u{1F451}\s*확인\s*완료/iu },
|
|
855
872
|
{ kind: 'decision', signal: TRACK_AGENT_FORBIDDEN_ACTION_REGEX, source: 'task' },
|
|
856
|
-
{ kind: 'decision', signal: /(?:마켓|레지스트리)(?:에|로)?\s*(?:게시|등록|발행)|사이트\s*운영\s*반영|SNS(?:에|로)?\s*(?:게시|발행|업로드)|공개\s*발행/iu, source: '
|
|
873
|
+
{ kind: 'decision', signal: /(?:마켓|레지스트리)(?:에|로)?\s*(?:게시|등록|발행)|사이트\s*운영\s*반영|SNS(?:에|로)?\s*(?:게시|발행|업로드)|공개\s*발행/iu, source: 'task' },
|
|
857
874
|
{ kind: 'harness', signal: /(?:마켓|레지스트리|사이트|SNS|공개)?(?:에|로)?\s*(?:게시|발행|배포|등록|운영\s*반영)(?:를?\s*위한)?\s*(?:준비|초안|원고|자료|구현|점검|테스트)|(?:마켓|레지스트리|사이트|SNS|공개)?(?:에|로)?\s*(?:게시|발행|배포|등록)(?:용|를?\s*위한)\s*(?:초안|원고|자료|코드|기능)/iu, source: 'task' },
|
|
858
875
|
];
|
|
859
876
|
|
|
@@ -861,9 +878,9 @@ export function planAction(item: SeatItem, seat?: string): { kind: 'decision' |
|
|
|
861
878
|
const task = `${item.id}\n${item.title}\n${item.text}`;
|
|
862
879
|
const content = `${task}\n${item.evidence ?? ''}`;
|
|
863
880
|
const preparation = SEAT_ACTION_RULES.find(({ kind }) => kind === 'harness')!;
|
|
864
|
-
const prepared = [...
|
|
881
|
+
const prepared = [...item.text.matchAll(new RegExp(preparation.signal.source, `${preparation.signal.flags.replace('g', '')}g`))];
|
|
865
882
|
const rule = SEAT_ACTION_RULES.map(({ kind, signal, source, resolvedBy }) => {
|
|
866
|
-
const text = source === 'content' ? content : task;
|
|
883
|
+
const text = kind === 'decision' ? item.text : source === 'content' ? content : task;
|
|
867
884
|
if (resolvedBy?.test(item.evidence ?? '')) return null;
|
|
868
885
|
const hits = [...text.matchAll(new RegExp(signal.source, `${signal.flags.replace('g', '')}g`))];
|
|
869
886
|
const hit = kind === 'decision' ? hits.find((match) => !prepared.some((prep) =>
|
|
@@ -936,13 +953,16 @@ function judgeSeatNeighbors(seat: string, config: SeatLoopConfig, deps: SeatDeps
|
|
|
936
953
|
if (judgment.state === 'absent') {
|
|
937
954
|
if (config.mode === 'live-safe' && judgment.action?.action === 'escalate' && isRescueAllowedAction('decision-card')) {
|
|
938
955
|
const ref = `seat-loop:neighbor:${seat}:${neighbor.id}:${lastSeenAt}`;
|
|
956
|
+
const options = [{ key: 'a', label: '사람 판단 후 별도 조치', consequence: '자리 루프는 대행하지 않는다' },
|
|
957
|
+
{ key: 'b', label: '현상 유지', consequence: '결측 상태를 계속 관측한다' }];
|
|
958
|
+
const recommendation = { option: 'b', why: '새 사건을 확인하기 전에는 이웃 업무를 대행하지 않는다' };
|
|
939
959
|
cardLedger(root, deps, now).raiseOnce({ title: `${seat}: 이웃 ${neighbor.id} 결측`, category: 'other',
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
960
|
+
...decisionCardMaterial({ what: `${seat} 이웃 ${neighbor.id} 결측`, soFar: '마지막 맥락 버스 사건 이후 새 ACK 없음',
|
|
961
|
+
current: `현재 시각 ${now.toISOString()}; 마지막 사건 ${lastSeenAt}; heartbeat ${neighbor.heartbeat.everyMinutes}분 × ${neighbor.heartbeat.missedTicks}회 결측`,
|
|
962
|
+
question: '결측 이웃의 업무를 어떻게 처리할까?', answer: '새 ACK까지 현상 유지하고 이웃 업무는 대행하지 않는다',
|
|
963
|
+
options, recommendation, confidence: '중간 — 사건의 부재만 관측됨',
|
|
964
|
+
ifUntouched: '결측 상태를 계속 관측하며 대행은 하지 않는다', evidence: `맥락 버스 마지막 사건 ${lastSeenAt}; ${ref}` }),
|
|
965
|
+
options, recommendation,
|
|
946
966
|
raisedBy: { agent: 'seat-loop' }, refs: [ref], raisedAt: now.toISOString(),
|
|
947
967
|
crossCheckSkipped: '이웃 결측으로 교차 확인 불가' }, ref);
|
|
948
968
|
action = 'decision-card';
|
|
@@ -1012,10 +1032,17 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
|
|
|
1012
1032
|
const question = proposal.inquiry?.question ?? proposal.item!.title;
|
|
1013
1033
|
const note = `${proposal.xcheckNote ?? ''} — 해결 가능하다고 했으나 ${waited}분 안 해소 안 됨`.trim();
|
|
1014
1034
|
if (!decisionReceipt(stateRoot, seat, proposal.item!)) {
|
|
1035
|
+
const options = [{ key: 'a', label: '승인', consequence: '사람 판단으로 진행' }, { key: 'b', label: '보류', consequence: '진행하지 않음' }];
|
|
1036
|
+
const recommendation = { option: 'b', why: `기한 ${waited}분 뒤에도 ${cell?.status ?? '질문 미해결'}이므로 착지 확인 전에는 진행하지 않는다` };
|
|
1037
|
+
const material = decisionCardMaterial({ what: `${seat} 질문 · ${proposal.item!.title}`, soFar: `이웃 ${neighbor} 해결 제안: ${proposal.xcheckNote ?? '기록 없음'}`,
|
|
1038
|
+
current: `기한 ${waited}분 경과 · 칸 상태 ${cell?.status ?? '질문 미해결'}`, question, answer: '재측상 미해결이므로 보류를 권고한다',
|
|
1039
|
+
options, recommendation, confidence: '중간 — 기한 경과와 미해결을 재측함', ifUntouched: '질문/칸은 미해결로 남는다',
|
|
1040
|
+
evidence: `${proposal.item!.evidence ?? '원천 근거 미기재'} · ${note} · ${decisionRef(seat, proposal.item!)}` });
|
|
1015
1041
|
await (deps.run ?? defaultRun)(['decisions', 'raise', '--title', `${seat}: ${question}`, '--category', 'other',
|
|
1016
|
-
'--s',
|
|
1042
|
+
'--s', material.scqa.s, '--c', material.scqa.c, '--q', material.scqa.q, '--a', material.scqa.a,
|
|
1043
|
+
'--pending-question', material.pendingQuestion,
|
|
1017
1044
|
'--option', 'a=승인:사람 판단으로 진행', '--option', 'b=보류:진행하지 않음',
|
|
1018
|
-
'--
|
|
1045
|
+
'--recommend', recommendation.option, '--why', recommendation.why, '--xcheck', `${neighbor}:${note}`,
|
|
1019
1046
|
'--agent', 'seat-loop', '--ref', decisionRef(seat, proposal.item!), '--json']);
|
|
1020
1047
|
}
|
|
1021
1048
|
const entry: SeatEntry = { ...proposal, ts: now.toISOString(), at: now.toISOString(), status: 'hitl' };
|
|
@@ -1313,10 +1340,18 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
|
|
|
1313
1340
|
const check = answer ? ['--xcheck', `${requestedNeighbor}:${answer.note}`,
|
|
1314
1341
|
...(answer.agree ? [] : ['--dissent', `${requestedNeighbor}: ${answer.note}`])]
|
|
1315
1342
|
: ['--no-xcheck', `이웃 ${requestedNeighbor} 무응답 ${entry.xcheckWaitMinutes}분`];
|
|
1343
|
+
const options = [{ key: 'a', label: '승인', consequence: '사람 판단으로 진행' }, { key: 'b', label: '보류', consequence: '진행하지 않음' }];
|
|
1344
|
+
const recommendation = { option: 'b', why: '교차 확인에서 해소 근거가 확보되지 않았다' };
|
|
1345
|
+
const material = decisionCardMaterial({ what: `${seat} 질문 · ${item.title}`, soFar: `이웃 ${requestedNeighbor}에 교차 확인 요청`,
|
|
1346
|
+
current: answer ? `이웃 응답: ${answer.note}` : `이웃 ${requestedNeighbor} 무응답 ${entry.xcheckWaitMinutes}분`,
|
|
1347
|
+
question: ask.question, answer: '해소 근거 확인 전까지 보류를 권고한다', options, recommendation,
|
|
1348
|
+
confidence: answer ? '중간 — 이웃 응답을 확인함' : '낮음 — 기한 내 이웃 응답 없음',
|
|
1349
|
+
ifUntouched: '사람 판단 전 집행하지 않는다', evidence: `${item.evidence ?? '원천 근거 미기재'} · ${decisionRef(seat, item)}` });
|
|
1316
1350
|
await (deps.run ?? defaultRun)(['decisions', 'raise', '--title', `${seat}: ${ask.question}`, '--category', 'other',
|
|
1317
|
-
'--s',
|
|
1351
|
+
'--s', material.scqa.s, '--c', material.scqa.c, '--q', material.scqa.q, '--a', material.scqa.a,
|
|
1352
|
+
'--pending-question', material.pendingQuestion,
|
|
1318
1353
|
'--option', 'a=승인:사람 판단으로 진행', '--option', 'b=보류:진행하지 않음',
|
|
1319
|
-
'--
|
|
1354
|
+
'--recommend', recommendation.option, '--why', recommendation.why, ...check, '--agent', 'seat-loop', '--ref', decisionRef(seat, item), '--json']);
|
|
1320
1355
|
if (!decisionReceipt(root, seat, item)) throw new Error('decisions raise did not deliver a matching card');
|
|
1321
1356
|
entry.status = 'hitl';
|
|
1322
1357
|
xcheckEvent('raised', seat, requestedNeighbor, itemKey(item), answer ? answer.agree ? 'agree' : 'dissent' : 'timed-out');
|
|
@@ -1442,11 +1477,27 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
|
|
|
1442
1477
|
if (action.kind === 'decision') {
|
|
1443
1478
|
const title = `${seat}: ${decisionSummary(action.text)}`;
|
|
1444
1479
|
const source = decisionSourceRef(seat, item);
|
|
1480
|
+
const options = [{ key: 'a', label: '사람 승인', consequence: '승인 후 별도로 집행' }, { key: 'b', label: '보류', consequence: '집행하지 않음' }];
|
|
1481
|
+
const recommendation = { option: 'b', why: `요청에서 걸린 문면 «${action.reason}»은 자동 집행 대상이 아니므로 보류한다` };
|
|
1482
|
+
let currentStatus: string | undefined;
|
|
1483
|
+
try {
|
|
1484
|
+
currentStatus = item.source === 'checklist' && item.version
|
|
1485
|
+
? (deps.checklistItems ?? ((version: string) => listChecklist(version, deps.root ?? releaseLedgerRoot()).items))(item.version)
|
|
1486
|
+
.find((cell) => cell.id === item.id && cell.owner === seat)?.status
|
|
1487
|
+
: item.source === 'request' ? listSeatRequests(stateRoot).find((row) => row.key === item.id && row.seat === seat)?.status
|
|
1488
|
+
: undefined;
|
|
1489
|
+
} catch (error) { observe('decision-source-unreadable', { seat, item: item.id, error: String(error).slice(0, 200) }); }
|
|
1490
|
+
const material = decisionCardMaterial({ what: `${seat} 배정 항목: ${item.title} 역할: ${inputs.role || '역할 근거 없음'}`, soFar: `요청 내용: ${item.text}`,
|
|
1491
|
+
current: `밑바탕 재측: ${currentStatus ?? '상태 미확인'}; 요청에서 금지 문면 «${action.reason}» 발견 — 실행 전 사람 승인 필요`,
|
|
1492
|
+
question: `금지 문면 «${action.reason}»이 걸린 요청을 승인할까?`, answer: '승인 전에는 집행하지 않고 보류한다',
|
|
1493
|
+
options, recommendation, confidence: '높음 — 요청에 금지 문면 직접 일치',
|
|
1494
|
+
ifUntouched: '자동 집행되지 않고 해당 칸은 보류 상태로 남는다',
|
|
1495
|
+
evidence: `${item.evidence?.trim() || '추가 근거 미기재'} · 요청 ${source} · 걸린 낱말 ${action.reason}` });
|
|
1445
1496
|
const raised = await run(['decisions', 'raise', '--title', title, '--category', 'other',
|
|
1446
|
-
'--s',
|
|
1447
|
-
'--
|
|
1497
|
+
'--s', material.scqa.s, '--c', material.scqa.c, '--q', material.scqa.q, '--a', material.scqa.a,
|
|
1498
|
+
'--pending-question', material.pendingQuestion,
|
|
1448
1499
|
'--option', 'a=사람 승인:승인 후 별도로 집행', '--option', 'b=보류:집행하지 않음',
|
|
1449
|
-
'--
|
|
1500
|
+
'--recommend', recommendation.option, '--why', recommendation.why, '--no-xcheck', '자리 루프 · 이웃 교환은 DEC-XCHECK ②',
|
|
1450
1501
|
'--agent', 'seat-loop', '--ref', source, '--json']);
|
|
1451
1502
|
const decision: unknown = JSON.parse(raised.trim());
|
|
1452
1503
|
if (!decision || typeof decision !== 'object' || typeof (decision as { id?: unknown }).id !== 'string') {
|
|
@@ -1461,25 +1512,38 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
|
|
|
1461
1512
|
const limit = repeatStopLimit(config);
|
|
1462
1513
|
const attempts = unlandedAttemptCount(seat, item, ledger, stateRoot, deps);
|
|
1463
1514
|
if (attempts >= limit && !personReopened(ledger, seat, item, stateRoot)) {
|
|
1515
|
+
// Re-read the underlying item at the decision boundary, not the picked snapshot.
|
|
1516
|
+
const current = item.source === 'checklist' && item.version
|
|
1517
|
+
? (deps.checklistItems ?? ((version: string) => listChecklist(version, deps.root ?? releaseLedgerRoot()).items))(item.version)
|
|
1518
|
+
.find((cell) => cell.id === item.id && cell.owner === seat)?.status
|
|
1519
|
+
: item.source === 'request' ? listSeatRequests(stateRoot).find((row) => row.key === item.id && row.seat === seat)?.status
|
|
1520
|
+
: undefined;
|
|
1521
|
+
if (current === 'green' || current === 'done') {
|
|
1522
|
+
entry.status = 'resolved';
|
|
1523
|
+
entry.reason = `밑바탕 재측: ${current} — 반복 정지 카드 불필요`;
|
|
1524
|
+
append(path, entry);
|
|
1525
|
+
observe('repeat-recovered', { seat, item: item.id, status: current });
|
|
1526
|
+
return entry;
|
|
1527
|
+
}
|
|
1464
1528
|
const reason = repeatStopReason(attempts);
|
|
1465
1529
|
entry.status = 'held';
|
|
1466
1530
|
entry.reason = reason;
|
|
1467
1531
|
const ref = decisionRef(seat, item);
|
|
1468
1532
|
const prior = new DecisionLedger({ stateDir: stateRoot }).list({ status: 'all' }).find((card) => card.refs?.includes(ref) && card.status === 'open');
|
|
1469
1533
|
if (!prior) {
|
|
1534
|
+
const options = [
|
|
1535
|
+
{ key: 'a', label: '다시 연다', consequence: '결정 뒤에 자리 루프가 한 번 더 시도할 수 있다' },
|
|
1536
|
+
{ key: 'b', label: '멈춘다', consequence: '같은 칸은 다시 발사하지 않는다' },
|
|
1537
|
+
];
|
|
1538
|
+
const recommendation = { option: 'b', why: `착지 없는 ${attempts}회 재시도보다 현재 칸을 멈추는 편이 중복 실행을 피한다` };
|
|
1470
1539
|
cardLedger(stateRoot, deps, now).raiseOnce({
|
|
1471
|
-
title: `${seat}: ${item.id} 반복 착지 0`,
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
options: [
|
|
1479
|
-
{ key: 'a', label: '다시 연다', consequence: '결정 뒤에 자리 루프가 한 번 더 시도할 수 있다' },
|
|
1480
|
-
{ key: 'b', label: '멈춘다', consequence: '같은 칸은 다시 발사하지 않는다' },
|
|
1481
|
-
],
|
|
1482
|
-
recommendation: { skipped: true, reason: '착지 없는 반복만으로 계속할지 자동 결정하지 않는다' },
|
|
1540
|
+
title: `${seat}: ${item.id} 반복 착지 0`, category: 'other',
|
|
1541
|
+
...decisionCardMaterial({ what: `${seat} 칸 ${item.id} · ${item.title}`, soFar: `${attempts}회 착지 없이 종료`,
|
|
1542
|
+
current: `발행 직전 밑바탕 재측: ${current ?? '상태 미확인'}; 같은 칸 착지 0`,
|
|
1543
|
+
question: '이 칸을 계속할까, 멈출까?', answer: '중복 발사를 멈추고 사람의 재개 결정을 기다린다',
|
|
1544
|
+
options, recommendation, confidence: current ? '높음 — 밑바탕 상태 재측됨' : '낮음 — 밑바탕 상태 미확인',
|
|
1545
|
+
ifUntouched: '같은 칸은 다시 발사되지 않는다', evidence: `${item.evidence ?? '원천 근거 미기재'} · ${ref} · 완료되지 않은 시도 ${attempts}회` }),
|
|
1546
|
+
options, recommendation,
|
|
1483
1547
|
raisedBy: { agent: 'seat-loop' },
|
|
1484
1548
|
refs: [ref],
|
|
1485
1549
|
raisedAt: now.toISOString(),
|
|
@@ -35,6 +35,8 @@ import { dirname, isAbsolute as isAbsolutePath, relative as relativePath, resolv
|
|
|
35
35
|
import {
|
|
36
36
|
applyClarificationReply,
|
|
37
37
|
planClarificationIntake,
|
|
38
|
+
selfAnswerGoalDocumentClarifications,
|
|
39
|
+
type GoalClarificationSelfResolver,
|
|
38
40
|
} from '../self-implement/goal-author-clarification.js';
|
|
39
41
|
import { classifyReauthoredAsk, countMissingAuthoredConstraintMarkers, askGoalTypeDeclaration, declaredGoalType, parseGoalId } from '../self-implement/goal-author.js';
|
|
40
42
|
import { observeFrontNodeEntry } from './graph-front-nodes.js';
|
|
@@ -143,6 +145,9 @@ export interface AskLaunchFlowDeps {
|
|
|
143
145
|
/** Optional structured clarification adapter. Omitted callers retain the
|
|
144
146
|
* existing CLI line-input contract exactly. */
|
|
145
147
|
readClarification?(clarification: GoalDocumentClarification): Promise<string>;
|
|
148
|
+
selfResolveClarification?: GoalClarificationSelfResolver;
|
|
149
|
+
/** Replaces the goal document after a self-answer; production entrances pass an atomic writer. */
|
|
150
|
+
writeGoalDocument?(path: string, document: string): void;
|
|
146
151
|
readFile(path: string): string;
|
|
147
152
|
writeFile(path: string, data: string): void;
|
|
148
153
|
cwd(): string;
|
|
@@ -188,6 +193,7 @@ export interface AskLaunchFlowInput {
|
|
|
188
193
|
readonly askFile?: string;
|
|
189
194
|
/** Optional repository root used only for code grounding during goal authoring. */
|
|
190
195
|
readonly groundingCwd?: string;
|
|
196
|
+
readonly authorGrade?: import('../self-implement/goal-author.js').GoalAuthorGradeSelection;
|
|
191
197
|
readonly liveRunWindowMinutes: number;
|
|
192
198
|
readonly recentChangeWindowDays: number;
|
|
193
199
|
readonly forceRequested: boolean;
|
|
@@ -724,16 +730,27 @@ async function intakeClarifications(
|
|
|
724
730
|
return effectiveGoalFile;
|
|
725
731
|
}
|
|
726
732
|
if (mode === 'deferred-noninteractive') {
|
|
727
|
-
//
|
|
728
|
-
//
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
+
// The resolver comes in through deps (I/O seam): production entrances pass the author
|
|
734
|
+
// self-resolver; a caller that injects none keeps the previous deferred behaviour.
|
|
735
|
+
if (selfResolveClarifications.value && deps.selfResolveClarification) try {
|
|
736
|
+
const resolution = await selfAnswerGoalDocumentClarifications(document, {
|
|
737
|
+
path: 'ask', ask: input.askText, resolver: deps.selfResolveClarification,
|
|
738
|
+
});
|
|
739
|
+
if (resolution.newlyAnswered > 0) {
|
|
740
|
+
(deps.writeGoalDocument ?? deps.writeFile)(effectiveGoalFile, resolution.document);
|
|
741
|
+
document = resolution.document;
|
|
742
|
+
}
|
|
743
|
+
} catch (error) {
|
|
744
|
+
deps.log('ask-clarify', { goalFile: effectiveGoalFile, reason: 'self-answer-failed', error: error instanceof Error ? error.message : String(error) }, 'warn');
|
|
745
|
+
}
|
|
746
|
+
const remaining = planClarificationIntake(document, false).pending;
|
|
747
|
+
// 미답으로 남은 문항만 기존 답변 창구를 안내한다.
|
|
748
|
+
deps.print(`[ask] ⑴b ⚠️ 미답 되묻기 ${remaining.length}건 — 저작은 그대로 간다 · 구현 전에 LLM 릴레이가 먼저 답하고, 못 하면 미답으로 진행한다 (사람을 기다리지 않는다)`);
|
|
749
|
+
for (const item of remaining) {
|
|
733
750
|
deps.print(`[ask] ❓ [${item.questionId}] ${item.question}`);
|
|
734
751
|
item.options.forEach((option, index) => deps.print(`[ask] ${index}) ${option.label}`));
|
|
735
752
|
}
|
|
736
|
-
if (
|
|
753
|
+
if (remaining.length > 0) {
|
|
737
754
|
const goalArg = deps.relativeToCwd(effectiveGoalFile);
|
|
738
755
|
deps.print(`[ask] ▶ 답하려면: elanous self clarify answer ${goalArg} <questionId> <옵션번호> (자유 답은 --other "<문장>")`);
|
|
739
756
|
deps.print(`[ask] ▶ 답한 뒤 재저작: elanous self author --supersedes ${goalArg}`);
|
|
@@ -771,6 +788,7 @@ async function intakeClarifications(
|
|
|
771
788
|
const reauthored = await deps.authorGoal([], {
|
|
772
789
|
cwd: deps.cwd(),
|
|
773
790
|
...(input.groundingCwd === undefined ? {} : { groundingCwd: input.groundingCwd }),
|
|
791
|
+
...(input.authorGrade === undefined ? {} : { authorGrade: input.authorGrade }),
|
|
774
792
|
fromClarification: `${deps.relativeToCwd(effectiveGoalFile)}#${seedQuestionId}`,
|
|
775
793
|
reauthorFromAnsweredClarification: true,
|
|
776
794
|
selfResolveClarifications: selfResolveClarifications.value,
|
|
@@ -853,6 +871,7 @@ export async function runAskLaunchFlow(
|
|
|
853
871
|
const authored = await deps.authorGoal([input.askText], {
|
|
854
872
|
cwd: deps.cwd(),
|
|
855
873
|
...(input.groundingCwd === undefined ? {} : { groundingCwd: input.groundingCwd }),
|
|
874
|
+
...(input.authorGrade === undefined ? {} : { authorGrade: input.authorGrade }),
|
|
856
875
|
...(goalType === null ? {} : { goalType }),
|
|
857
876
|
// ⛔ 인라인 ask(`harness say`)는 askFile 이 없다 — 그 칸을 넘기면 저작기가 빈 줄을 쓴다.
|
|
858
877
|
...(() => {
|
|
@@ -28,6 +28,7 @@ import type { RunIdSource } from '../harness/harness-space.js';
|
|
|
28
28
|
import { withFileLockSync } from '../storage/file-lock.js';
|
|
29
29
|
import type { SelfDevJobResult, SelfDevGoal } from './orchestrate.js';
|
|
30
30
|
import type { SupervisorStopReason } from './run-supervisor.js';
|
|
31
|
+
import type { SupervisorNext } from './supervisor-verdict-edges.js';
|
|
31
32
|
import { loadMirroredRunRecord, mirrorRunRecord } from './run-record-mirror.js';
|
|
32
33
|
|
|
33
34
|
export interface SelfDevRunParticipant {
|
|
@@ -92,6 +93,8 @@ export interface SelfDevRunState {
|
|
|
92
93
|
parkedResolution?: SelfDevRunResolution;
|
|
93
94
|
/** The closed supervisor termination reason, persisted independently from per-goal results. */
|
|
94
95
|
supervisorStopReason?: SupervisorStopReason;
|
|
96
|
+
/** Named next step at supervisor termination; legacy checkpoints omit it. */
|
|
97
|
+
next?: SupervisorNext;
|
|
95
98
|
/** ★ C(2026-07-21·HITL 정황 품질) — 이 run 을 구동하는 오케스트레이터 프로세스 pid. 살아있으면
|
|
96
99
|
* non-terminal goal 은 **실행중(running)** 이지 막힌(interrupted) 게 아니다. 죽었으면(오케스트레이터
|
|
97
100
|
* killed) 진짜 interrupted. isPidAlive 로 구분해 라이브 잡을 parked/repair-signals 에서 제외. */
|
|
@@ -311,13 +314,14 @@ export function recordSelfDevRunSupervisorStop(
|
|
|
311
314
|
runId: string,
|
|
312
315
|
supervisorStopReason: SupervisorStopReason,
|
|
313
316
|
dir = selfDevRunsDir(),
|
|
317
|
+
next?: SupervisorNext,
|
|
314
318
|
): SupervisorStopPersistenceOutcome {
|
|
315
319
|
try {
|
|
316
320
|
if (!existsSync(dir)) return { outcome: 'missing-directory' };
|
|
317
321
|
return withFileLockSync(join(dir, `${runId}.lock`), () => {
|
|
318
322
|
const state = loadSelfDevRunFromPath(runPath(runId, dir));
|
|
319
323
|
if (!state) return { outcome: 'missing-record' };
|
|
320
|
-
writeSelfDevRun({ ...state, updatedAt: Date.now(), supervisorStopReason }, dir);
|
|
324
|
+
writeSelfDevRun({ ...state, updatedAt: Date.now(), supervisorStopReason, ...(next === undefined ? {} : { next }) }, dir);
|
|
321
325
|
return { outcome: 'persisted' };
|
|
322
326
|
});
|
|
323
327
|
} catch {
|
|
@@ -6,6 +6,7 @@ import type { SelfDevJobResult } from './orchestrate.js';
|
|
|
6
6
|
import { recordSelfDevRunSupervisorStop, selfDevRunsDir } from './run-store.js';
|
|
7
7
|
import { debug } from '../debug/log.js';
|
|
8
8
|
import { sweepFrozenMerges } from '../self-implement/frozen-merges.js';
|
|
9
|
+
import { routeSupervisorVerdict, type SupervisorNext } from './supervisor-verdict-edges.js';
|
|
9
10
|
|
|
10
11
|
export interface SupervisorRound {
|
|
11
12
|
round: number;
|
|
@@ -186,6 +187,7 @@ function formatGoalPlanRevisionObservation(results: readonly SelfDevJobResult[])
|
|
|
186
187
|
export type SupervisorJobResult = SelfDevJobResult & {
|
|
187
188
|
reviewReason?: string;
|
|
188
189
|
salvage?: 'launched' | 'parked';
|
|
190
|
+
next?: SupervisorNext;
|
|
189
191
|
};
|
|
190
192
|
|
|
191
193
|
export function decideNextRun(input: {
|
|
@@ -592,9 +594,13 @@ export async function superviseRun(opts: SuperviseRunOptions): Promise<Superviso
|
|
|
592
594
|
} catch { /* fail-open */ }
|
|
593
595
|
|
|
594
596
|
if (decision.action === 'stop') {
|
|
597
|
+
const reason = decision.stopReason ?? 'unregistered-stop';
|
|
598
|
+
const next = routeSupervisorVerdict(reason, results).next;
|
|
599
|
+
try { debug.log('self-dev.supervisor', 'verdict-edge', { runId, reason, next }); } catch { /* observation must not interrupt supervision */ }
|
|
600
|
+
results = results.map((result) => ({ ...result, next }));
|
|
595
601
|
if (runId && decision.stopReason) {
|
|
596
602
|
const storage = resolveSupervisorStopStorage(opts.runStore);
|
|
597
|
-
const persistence = recordSelfDevRunSupervisorStop(runId, decision.stopReason, storage.path);
|
|
603
|
+
const persistence = recordSelfDevRunSupervisorStop(runId, decision.stopReason, storage.path, next);
|
|
598
604
|
const storageObservation = {
|
|
599
605
|
storagePath: storage.path,
|
|
600
606
|
storageSource: storage.source,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { SelfDevJobResult } from './orchestrate.js';
|
|
2
|
+
import type { SupervisorStopReason } from './run-supervisor.js';
|
|
3
|
+
|
|
4
|
+
export type SupervisorNext = 'harvest' | 'scope-decompose' | 'self-review' | 'human-gate' | 'proposal';
|
|
5
|
+
export type SupervisorVerdictEdge = { readonly meaning: string; readonly next: SupervisorNext };
|
|
6
|
+
|
|
7
|
+
// 목표 규칙: 사람 관문은 결제·공개·자격·비가역 결정에만. 지금 max-rounds·provider-exhausted·step-timeout 이 human-gate 인 것은
|
|
8
|
+
// 그 규칙의 예외로 «그대로» 둔 값이다(시험에 고정 — 바꾸면 보인다).
|
|
9
|
+
export const SUPERVISOR_VERDICT_EDGES = {
|
|
10
|
+
converged: { meaning: 'run converged', next: 'harvest' },
|
|
11
|
+
'human-stopped': { meaning: 'human stopped the run', next: 'human-gate' },
|
|
12
|
+
'parent-signals-red': { meaning: 'parent decision signals are red', next: 'human-gate' },
|
|
13
|
+
'needs-human': { meaning: 'unresolved human or harvest decision', next: 'human-gate' },
|
|
14
|
+
'no-actionable-work': { meaning: 'no actionable work remains', next: 'self-review' },
|
|
15
|
+
'harvestable-awaiting-human': { meaning: 'implementation awaits harvest', next: 'harvest' },
|
|
16
|
+
'handed-off-to-salvage': { meaning: 'salvage already owns the work', next: 'harvest' },
|
|
17
|
+
'max-rounds': { meaning: 'round budget exhausted', next: 'human-gate' },
|
|
18
|
+
'no-progress': { meaning: 'run stalled', next: 'scope-decompose' },
|
|
19
|
+
'provider-exhausted': { meaning: 'provider quota exhausted', next: 'human-gate' },
|
|
20
|
+
'step-timeout': { meaning: 'step budget exhausted', next: 'human-gate' },
|
|
21
|
+
'decomposable-no-progress': { meaning: 'stalled with a decomposition proposal', next: 'scope-decompose' },
|
|
22
|
+
'review-unobserved': { meaning: 'review was not observed', next: 'self-review' },
|
|
23
|
+
'deliverable-unobserved': { meaning: 'deliverable was not observed', next: 'self-review' },
|
|
24
|
+
'deliverable-merged': { meaning: 'deliverable is merged', next: 'harvest' },
|
|
25
|
+
} as const satisfies Record<SupervisorStopReason, SupervisorVerdictEdge>;
|
|
26
|
+
|
|
27
|
+
const HUMAN_GATE_MERGE_REASONS: ReadonlySet<string> = new Set(['decision-signal-red', 'signal-incomplete', 'parent-unlanded', 'human-gate']);
|
|
28
|
+
|
|
29
|
+
/** Unknown stop vocabularies are proposals, never silently treated as approved edges. */
|
|
30
|
+
export function routeSupervisorVerdict(
|
|
31
|
+
reason: string,
|
|
32
|
+
results: readonly Partial<Pick<SelfDevJobResult, 'status' | 'stage' | 'merged' | 'prNumber' | 'prUrl' | 'blockReason' | 'mergeReason'>>[] = [],
|
|
33
|
+
): SupervisorVerdictEdge {
|
|
34
|
+
const edge = Object.hasOwn(SUPERVISOR_VERDICT_EDGES, reason)
|
|
35
|
+
? SUPERVISOR_VERDICT_EDGES[reason as SupervisorStopReason]
|
|
36
|
+
: undefined;
|
|
37
|
+
if (!edge) return { meaning: 'unregistered supervisor stop reason', next: 'proposal' };
|
|
38
|
+
if (reason !== 'needs-human') return edge;
|
|
39
|
+
if (results.some((result) => result.blockReason === 'parent-unlanded')) return edge;
|
|
40
|
+
// A human-gate reason on ANY result in the run keeps the whole run at the human gate.
|
|
41
|
+
if (results.some((result) => HUMAN_GATE_MERGE_REASONS.has(String(result.mergeReason)))) return edge;
|
|
42
|
+
const openPrs = results.filter((result) => result.status === 'done' && result.stage === 'pr-opened'
|
|
43
|
+
&& result.merged !== true && (result.prNumber !== undefined || Boolean(result.prUrl)));
|
|
44
|
+
if (openPrs.length === 0) return edge;
|
|
45
|
+
return { meaning: 'open PR ready for harvest', next: 'harvest' };
|
|
46
|
+
}
|
|
@@ -2,9 +2,9 @@ import { closeSync, existsSync, mkdirSync, openSync, readFileSync, realpathSync,
|
|
|
2
2
|
import { dlopen, FFIType } from 'bun:ffi';
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
4
|
import { dirname, join, resolve } from 'node:path';
|
|
5
|
-
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
5
|
+
import { effectiveInstanceRoot, prodInstanceRoot } from '../instance/resolve.js';
|
|
6
6
|
import { debug } from '../debug/log.js';
|
|
7
|
-
import { beginLandingMerge,
|
|
7
|
+
import { beginLandingMerge, type LandingFreeze } from '../release-loop/landing-freeze.js';
|
|
8
8
|
|
|
9
9
|
/** `manual`: the owning repository could not be determined — a sweep never resumes it; a person does. */
|
|
10
10
|
export interface FrozenMerge { prNumber: number; headCommit: string; repoRoot: string; manual?: true; /** Host goal document, so a resumed merge can still apply its «이 칸 완료» declaration (CL-AUTO). */ goalFile?: string }
|
|
@@ -182,8 +182,10 @@ export function admitLandingMerge(
|
|
|
182
182
|
root = effectiveInstanceRoot(),
|
|
183
183
|
hooks: { afterQueue?: () => void } = {},
|
|
184
184
|
claimKey?: { prNumber: number; repoRoot: string; headCommit?: string },
|
|
185
|
+
opts: { prodFreezeRoot?: string; forceReason?: string } = {},
|
|
185
186
|
): LandingAdmission {
|
|
186
187
|
let entry: FrozenMerge | null | undefined;
|
|
188
|
+
const prodFreezeRoot = opts.prodFreezeRoot ?? prodInstanceRoot();
|
|
187
189
|
// A held PR is merged by exactly one runner: the claim (repository + PR) is held from admission until the lander's
|
|
188
190
|
// merge has ended. Its queue entry stays until that merge is confirmed — `end(true)` removes this lander's own head;
|
|
189
191
|
// a failed or interrupted merge leaves it for the next resume sweep.
|
|
@@ -201,7 +203,7 @@ export function admitLandingMerge(
|
|
|
201
203
|
} finally { landing.end(); held?.release(); }
|
|
202
204
|
};
|
|
203
205
|
for (;;) {
|
|
204
|
-
const landing = beginLandingMerge(root);
|
|
206
|
+
const landing = beginLandingMerge(root, new Date(), prodFreezeRoot, opts.forceReason);
|
|
205
207
|
if (!landing.frozen) {
|
|
206
208
|
if (!claim && claimKey) {
|
|
207
209
|
const key = { prNumber: claimKey.prNumber, repoRoot: repoIdentity(claimKey.repoRoot), headCommit: claimKey.headCommit };
|
|
@@ -222,17 +224,19 @@ export function admitLandingMerge(
|
|
|
222
224
|
if (!entry) return { kind: 'held', freeze: landing.frozen };
|
|
223
225
|
queueFrozenMerge(entry, root);
|
|
224
226
|
hooks.afterQueue?.();
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
227
|
+
const afterQueue = beginLandingMerge(root, new Date(), prodFreezeRoot, opts.forceReason);
|
|
228
|
+
if (afterQueue.frozen) return { kind: 'held', freeze: afterQueue.frozen };
|
|
229
|
+
try {
|
|
230
|
+
const mine = { ...entry, repoRoot: repoIdentity(entry.repoRoot) };
|
|
231
|
+
claim = claimFrozenMerge(mine, root);
|
|
232
|
+
if (!claim) return { kind: 'taken' };
|
|
233
|
+
// A resume sweep already merged and dequeued it between our queueing and our claim: nothing left to merge.
|
|
234
|
+
const stillHeld = readQueue(root).some((item) => sameEntry(item, mine));
|
|
235
|
+
if (!stillHeld) { claim.release(); return { kind: 'taken' }; }
|
|
236
|
+
ownHead = mine;
|
|
237
|
+
debug.log('harness.merge', 'freeze-lifted-while-queueing', { pr: entry.prNumber });
|
|
238
|
+
} catch (error) { claim?.release(); throw error; }
|
|
239
|
+
finally { afterQueue.end(); }
|
|
236
240
|
}
|
|
237
241
|
}
|
|
238
242
|
|
|
@@ -259,16 +263,17 @@ export async function sweepFrozenMerges(
|
|
|
259
263
|
},
|
|
260
264
|
root = effectiveInstanceRoot(),
|
|
261
265
|
alreadyMerged: (entry: FrozenMerge) => boolean | Promise<boolean> = githubMergedAt,
|
|
266
|
+
prodFreezeRoot = prodInstanceRoot(),
|
|
262
267
|
): Promise<{ pending: number; merged: number }> {
|
|
263
|
-
const frozen = readLandingFreeze(root);
|
|
264
|
-
if (frozen) return { pending: readQueue(root).length, merged: 0 };
|
|
265
268
|
const pending = readQueue(root);
|
|
266
269
|
let merged = 0;
|
|
267
270
|
for (const entry of pending) {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
+
const guard = beginLandingMerge(root, new Date(), prodFreezeRoot);
|
|
272
|
+
if (guard.frozen) break;
|
|
273
|
+
let claim: { release: () => void } | null = null;
|
|
271
274
|
try {
|
|
275
|
+
claim = claimFrozenMerge(entry, root);
|
|
276
|
+
if (!claim) continue; // another sweep or the original lander is on it
|
|
272
277
|
if (!readQueue(root).some((item) => sameEntry(item, entry))) continue; // taken back by its lander
|
|
273
278
|
if (entry.manual) { debug.log('harness.merge', 'resume-manual', { pr: entry.prNumber, repoRoot: entry.repoRoot }); continue; }
|
|
274
279
|
if (!(await alreadyMerged(entry))) {
|
|
@@ -289,7 +294,7 @@ export async function sweepFrozenMerges(
|
|
|
289
294
|
});
|
|
290
295
|
} catch (error) {
|
|
291
296
|
debug.log('harness.merge', 'resume-failed', { pr: entry.prNumber, error: String(error) });
|
|
292
|
-
} finally { claim
|
|
297
|
+
} finally { guard.end(); claim?.release(); }
|
|
293
298
|
}
|
|
294
299
|
return { pending: readQueue(root).length, merged };
|
|
295
300
|
}
|