@huseynsnmz/pi-suite 0.0.0-stage → 0.2.0
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/CHANGELOG.md +28 -0
- package/LICENSE +21 -0
- package/README.md +88 -2
- package/SECURITY.md +5 -0
- package/package.json +112 -4
- package/packages/board/CHANGELOG.md +340 -0
- package/packages/board/CONTRIBUTING.md +7 -0
- package/packages/board/LICENSE +21 -0
- package/packages/board/README.md +88 -0
- package/packages/board/SECURITY.md +19 -0
- package/packages/board/docs/how-it-works.md +31 -0
- package/packages/board/docs/reference.md +278 -0
- package/packages/board/package.json +81 -0
- package/packages/board/runner/job-runner-legacy.mjs +68 -0
- package/packages/board/runner/job-runner.mjs +834 -0
- package/packages/board/runner/pty-runner-legacy.mjs +50 -0
- package/packages/board/runner/pty-runner.mjs +2175 -0
- package/packages/board/runner/state-coordinator.mjs +673 -0
- package/packages/board/runner/state-runner.mjs +203 -0
- package/packages/board/runner/title-runner.mjs +80 -0
- package/packages/board/src/commands/agent-board.ts +394 -0
- package/packages/board/src/commands/attach-decision.mjs +66 -0
- package/packages/board/src/commands/attach-flow.ts +251 -0
- package/packages/board/src/commands/bg.ts +103 -0
- package/packages/board/src/commands/board-model.ts +21 -0
- package/packages/board/src/core/atomic.mjs +184 -0
- package/packages/board/src/core/attach-delete.mjs +24 -0
- package/packages/board/src/core/attach-frame-proof.mjs +870 -0
- package/packages/board/src/core/attach-frame-reporter.mjs +648 -0
- package/packages/board/src/core/attach-frame-stream.mjs +266 -0
- package/packages/board/src/core/attach-view-proof.mjs +709 -0
- package/packages/board/src/core/auto-state.mjs +377 -0
- package/packages/board/src/core/background-runs.mjs +273 -0
- package/packages/board/src/core/code-refs-store.mjs +315 -0
- package/packages/board/src/core/code-refs.mjs +913 -0
- package/packages/board/src/core/config.mjs +77 -0
- package/packages/board/src/core/control-protocol.mjs +482 -0
- package/packages/board/src/core/coordinator-client.mjs +633 -0
- package/packages/board/src/core/coordinator-journal.mjs +282 -0
- package/packages/board/src/core/coordinator-protocol.mjs +14 -0
- package/packages/board/src/core/cwd-stats.mjs +114 -0
- package/packages/board/src/core/dashboard-render.mjs +10 -0
- package/packages/board/src/core/derive.mjs +117 -0
- package/packages/board/src/core/diagnostics.mjs +133 -0
- package/packages/board/src/core/double-press.mjs +26 -0
- package/packages/board/src/core/editor-state-reporter.mjs +112 -0
- package/packages/board/src/core/events.mjs +286 -0
- package/packages/board/src/core/evidence.mjs +245 -0
- package/packages/board/src/core/follow-up-queue.mjs +211 -0
- package/packages/board/src/core/foreground-preview-cache.mjs +117 -0
- package/packages/board/src/core/foreground-projection-cache.mjs +99 -0
- package/packages/board/src/core/frame-witness-update.mjs +35 -0
- package/packages/board/src/core/heuristics.mjs +313 -0
- package/packages/board/src/core/hold-input.mjs +27 -0
- package/packages/board/src/core/host-coordination.mjs +300 -0
- package/packages/board/src/core/host-crash.mjs +79 -0
- package/packages/board/src/core/host-probe.mjs +207 -0
- package/packages/board/src/core/host-protocol.mjs +73 -0
- package/packages/board/src/core/ids.mjs +35 -0
- package/packages/board/src/core/ime-cursor-coalesce.mjs +193 -0
- package/packages/board/src/core/invocation.mjs +43 -0
- package/packages/board/src/core/launch-options.mjs +418 -0
- package/packages/board/src/core/launch.mjs +157 -0
- package/packages/board/src/core/load-config.mjs +4 -0
- package/packages/board/src/core/locks.mjs +530 -0
- package/packages/board/src/core/paths.mjs +183 -0
- package/packages/board/src/core/pid.mjs +75 -0
- package/packages/board/src/core/prewarm-schedule.mjs +41 -0
- package/packages/board/src/core/process-identity-probe.mjs +228 -0
- package/packages/board/src/core/prompt-transport.mjs +13 -0
- package/packages/board/src/core/pty-attach-jiggle-controller.mjs +356 -0
- package/packages/board/src/core/pty-attach-jiggle-retry.mjs +109 -0
- package/packages/board/src/core/pty-attach-reconnect.mjs +61 -0
- package/packages/board/src/core/pty-attach-render.mjs +229 -0
- package/packages/board/src/core/pty-links.mjs +81 -0
- package/packages/board/src/core/pty-scroll.mjs +522 -0
- package/packages/board/src/core/pty-support.mjs +327 -0
- package/packages/board/src/core/repo.mjs +103 -0
- package/packages/board/src/core/rows.mjs +414 -0
- package/packages/board/src/core/screen-log-gc.mjs +198 -0
- package/packages/board/src/core/screen-log.mjs +160 -0
- package/packages/board/src/core/session-view.mjs +174 -0
- package/packages/board/src/core/slash-commands.mjs +52 -0
- package/packages/board/src/core/state-commands.mjs +757 -0
- package/packages/board/src/core/status-consistency.mjs +98 -0
- package/packages/board/src/core/steering-prompts.mjs +34 -0
- package/packages/board/src/core/steering.mjs +133 -0
- package/packages/board/src/core/stop-latch.mjs +41 -0
- package/packages/board/src/core/store.mjs +523 -0
- package/packages/board/src/core/suspend.mjs +36 -0
- package/packages/board/src/core/terminal-attach-client.mjs +981 -0
- package/packages/board/src/core/terminal-attach-protocol.mjs +460 -0
- package/packages/board/src/core/terminal-model.mjs +222 -0
- package/packages/board/src/core/terminal-query-sequences.mjs +239 -0
- package/packages/board/src/core/terminal-reply-guard.mjs +222 -0
- package/packages/board/src/core/terminal-snapshot.mjs +440 -0
- package/packages/board/src/core/test-knobs.mjs +28 -0
- package/packages/board/src/core/title.mjs +43 -0
- package/packages/board/src/core/types.mjs +414 -0
- package/packages/board/src/core/warm-host-sweeper.mjs +168 -0
- package/packages/board/src/core/worktree.mjs +65 -0
- package/packages/board/src/index.ts +303 -0
- package/packages/board/src/runtime/service.mjs +3222 -0
- package/packages/board/src/ui/chrome.ts +20 -0
- package/packages/board/src/ui/dashboard-decisions.mjs +55 -0
- package/packages/board/src/ui/dashboard-evidence.mjs +85 -0
- package/packages/board/src/ui/dashboard.ts +2237 -0
- package/packages/board/src/ui/double-left.ts +30 -0
- package/packages/board/src/ui/external-editor.ts +25 -0
- package/packages/board/src/ui/focus.ts +32 -0
- package/packages/board/src/ui/pty-attach.ts +3184 -0
- package/packages/board/src/ui/waiting.ts +30 -0
- package/packages/commands/LICENSE +21 -0
- package/packages/commands/README.md +19 -0
- package/packages/commands/package.json +22 -0
- package/packages/commands/src/index.ts +43 -0
- package/packages/commands/src/security-review.ts +109 -0
- package/packages/frame/CHANGELOG.md +113 -0
- package/packages/frame/CONTRIBUTING.md +7 -0
- package/packages/frame/LICENSE +22 -0
- package/packages/frame/README.md +105 -0
- package/packages/frame/SECURITY.md +16 -0
- package/packages/frame/docs/how-it-works.md +53 -0
- package/packages/frame/docs/reference.md +78 -0
- package/packages/frame/package.json +72 -0
- package/packages/frame/src/data/git.ts +90 -0
- package/packages/frame/src/data/github.ts +140 -0
- package/packages/frame/src/data/gitlab.ts +63 -0
- package/packages/frame/src/data/mode.ts +49 -0
- package/packages/frame/src/data/past-prompts.ts +57 -0
- package/packages/frame/src/data/runner.ts +32 -0
- package/packages/frame/src/data/snapshot.ts +223 -0
- package/packages/frame/src/data/start-notes.ts +104 -0
- package/packages/frame/src/data/telemetry.ts +337 -0
- package/packages/frame/src/data/todos.ts +162 -0
- package/packages/frame/src/data/totals.ts +94 -0
- package/packages/frame/src/data/visits.ts +45 -0
- package/packages/frame/src/index.ts +696 -0
- package/packages/frame/src/kit/LICENSE +21 -0
- package/packages/frame/src/kit/app/attribution.ts +30 -0
- package/packages/frame/src/kit/app/builder.ts +186 -0
- package/packages/frame/src/kit/app/claim.ts +27 -0
- package/packages/frame/src/kit/app/feature.ts +91 -0
- package/packages/frame/src/kit/app/host.ts +214 -0
- package/packages/frame/src/kit/app/renderers.ts +62 -0
- package/packages/frame/src/kit/app/scope.ts +125 -0
- package/packages/frame/src/kit/board/model.ts +337 -0
- package/packages/frame/src/kit/contract.ts +79 -0
- package/packages/frame/src/kit/contracts/accounts.ts +33 -0
- package/packages/frame/src/kit/contracts/ask.ts +120 -0
- package/packages/frame/src/kit/contracts/board.ts +6 -0
- package/packages/frame/src/kit/contracts/by.ts +22 -0
- package/packages/frame/src/kit/contracts/calls.ts +46 -0
- package/packages/frame/src/kit/contracts/events.ts +89 -0
- package/packages/frame/src/kit/contracts/mode.ts +26 -0
- package/packages/frame/src/kit/contracts/names.ts +12 -0
- package/packages/frame/src/kit/contracts/peers.ts +77 -0
- package/packages/frame/src/kit/contracts/prompts.ts +8 -0
- package/packages/frame/src/kit/contracts/screen.ts +75 -0
- package/packages/frame/src/kit/contracts/skills.ts +81 -0
- package/packages/frame/src/kit/contracts/sources.ts +81 -0
- package/packages/frame/src/kit/contracts/views.ts +42 -0
- package/packages/frame/src/kit/control.ts +59 -0
- package/packages/frame/src/kit/dark-theme.ts +41 -0
- package/packages/frame/src/kit/decode.ts +210 -0
- package/packages/frame/src/kit/events/log.ts +313 -0
- package/packages/frame/src/kit/format.ts +25 -0
- package/packages/frame/src/kit/host.ts +4 -0
- package/packages/frame/src/kit/index.ts +25 -0
- package/packages/frame/src/kit/legacy-tasks.ts +39 -0
- package/packages/frame/src/kit/overlay-images.ts +36 -0
- package/packages/frame/src/kit/paths.ts +16 -0
- package/packages/frame/src/kit/screen/client.ts +74 -0
- package/packages/frame/src/kit/screen/feature.ts +64 -0
- package/packages/frame/src/kit/screen/model.ts +182 -0
- package/packages/frame/src/kit/screen/view.ts +838 -0
- package/packages/frame/src/kit/settings/files.ts +90 -0
- package/packages/frame/src/kit/settings/setting.ts +57 -0
- package/packages/frame/src/kit/settings/store.ts +192 -0
- package/packages/frame/src/kit/sources/file.ts +129 -0
- package/packages/frame/src/kit/sources/follow.ts +155 -0
- package/packages/frame/src/kit/testing.ts +260 -0
- package/packages/frame/src/kit/totals.ts +72 -0
- package/packages/frame/src/kit/ui/entry.ts +75 -0
- package/packages/frame/src/kit/ui/frame.ts +212 -0
- package/packages/frame/src/kit/ui/glyphs.ts +132 -0
- package/packages/frame/src/kit/ui/scroll.ts +14 -0
- package/packages/frame/src/render/diff.ts +157 -0
- package/packages/frame/src/render/fit.ts +86 -0
- package/packages/frame/src/render/format.ts +108 -0
- package/packages/frame/src/render/frame.ts +106 -0
- package/packages/frame/src/render/glyphs.ts +189 -0
- package/packages/frame/src/render/keys.ts +43 -0
- package/packages/frame/src/render/lead.ts +37 -0
- package/packages/frame/src/render/paint.ts +135 -0
- package/packages/frame/src/render/safe.ts +19 -0
- package/packages/frame/src/render/segments.ts +735 -0
- package/packages/frame/src/render/streaming.ts +223 -0
- package/packages/frame/src/render/timing.ts +106 -0
- package/packages/frame/src/render/tools.ts +167 -0
- package/packages/frame/src/settings.ts +105 -0
- package/packages/frame/src/ui/editor.ts +272 -0
- package/packages/frame/src/ui/effort.ts +36 -0
- package/packages/frame/src/ui/footer.ts +165 -0
- package/packages/frame/src/ui/header.ts +362 -0
- package/packages/frame/src/ui/history-search.ts +23 -0
- package/packages/frame/src/ui/interrupt.ts +21 -0
- package/packages/frame/src/ui/outputs.ts +170 -0
- package/packages/frame/src/ui/pi-entries.ts +298 -0
- package/packages/frame/src/ui/previews.ts +72 -0
- package/packages/frame/src/ui/queue.ts +75 -0
- package/packages/frame/src/ui/screen.ts +59 -0
- package/packages/frame/src/ui/timings.ts +48 -0
- package/packages/frame/src/ui/todos.ts +205 -0
- package/packages/frame/src/ui/tool-frame.ts +595 -0
- package/packages/frame/src/ui/working.ts +25 -0
- package/packages/frame/src/view/state.ts +97 -0
- package/packages/frame/src/view/transcript.ts +175 -0
- package/packages/frame/src/view/view.ts +162 -0
- package/packages/memory/CONTRIBUTING.md +7 -0
- package/packages/memory/LICENSE +21 -0
- package/packages/memory/README.md +44 -0
- package/packages/memory/SECURITY.md +20 -0
- package/packages/memory/package.json +55 -0
- package/packages/memory/skills/review-memory/SKILL.md +48 -0
- package/packages/memory/src/index.ts +163 -0
- package/packages/memory/src/prompt.ts +25 -0
- package/packages/memory/src/review.ts +150 -0
- package/packages/memory/src/secrets.ts +22 -0
- package/packages/memory/src/store.ts +173 -0
- package/packages/modes/CHANGELOG.md +104 -0
- package/packages/modes/CONTRIBUTING.md +7 -0
- package/packages/modes/LICENSE +21 -0
- package/packages/modes/README.md +174 -0
- package/packages/modes/SECURITY.md +17 -0
- package/packages/modes/package.json +55 -0
- package/packages/modes/skills/review-permissions/SKILL.md +54 -0
- package/packages/modes/src/bash.ts +218 -0
- package/packages/modes/src/deny-off.ts +62 -0
- package/packages/modes/src/git-safety.ts +72 -0
- package/packages/modes/src/index.ts +338 -0
- package/packages/modes/src/interview.ts +206 -0
- package/packages/modes/src/judge-gate.ts +113 -0
- package/packages/modes/src/modes.ts +121 -0
- package/packages/modes/src/review/apply.mjs +156 -0
- package/packages/modes/src/review/collect.mjs +208 -0
- package/packages/modes/src/review/tools.ts +71 -0
- package/packages/modes/src/sandbox.ts +37 -0
- package/packages/router/CHANGELOG.md +8 -0
- package/packages/router/LICENSE +21 -0
- package/packages/router/README.md +90 -0
- package/packages/router/package.json +51 -0
- package/packages/router/src/config.ts +65 -0
- package/packages/router/src/index.ts +172 -0
- package/packages/router/src/judge.ts +51 -0
- package/packages/router/src/route.ts +67 -0
- package/packages/web/CHANGELOG.md +36 -0
- package/packages/web/LICENSE +21 -0
- package/packages/web/README.md +87 -0
- package/packages/web/SECURITY.md +13 -0
- package/packages/web/package.json +59 -0
- package/packages/web/src/config.ts +109 -0
- package/packages/web/src/dnsfilter.ts +97 -0
- package/packages/web/src/extract.ts +65 -0
- package/packages/web/src/index.ts +217 -0
- package/packages/web/src/markdown.ts +122 -0
- package/packages/web/src/net.ts +109 -0
- package/packages/web/src/reader.ts +73 -0
- package/packages/web/src/sanitize.ts +50 -0
- package/packages/web/src/search.ts +76 -0
- package/packages/web/src/secrets.ts +22 -0
- package/packages/web/src/validate.ts +186 -0
- package/src/index.ts +156 -0
- package/src/settings.ts +79 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reader-side revision consistency for the (state.json, status.json) pair
|
|
3
|
+
* (issue #91, spec D3 — 根治条件 5 read side).
|
|
4
|
+
*
|
|
5
|
+
* The View State Coordinator materializes both artifacts under one shared
|
|
6
|
+
* `materializedRevision` inside a single view lock. Disagreeing stamps
|
|
7
|
+
* therefore mean a coordinator crashed between its paired writes; readers
|
|
8
|
+
* must not combine the mismatched halves into one decision — skip the
|
|
9
|
+
* combination and request coordinator repair (a fresh coordinator's boot
|
|
10
|
+
* replay re-materializes the half-written pair from the journal).
|
|
11
|
+
*
|
|
12
|
+
* Legacy rows (written before revisions existed, or never touched by a
|
|
13
|
+
* command) may lack the field on either side; per the spec's legacy-migration
|
|
14
|
+
* clause the check is skipped for them — a missing stamp is never a desync.
|
|
15
|
+
*/
|
|
16
|
+
import { readState as readStateImpl, readStatus as readStatusImpl } from "./store.mjs";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Whether the (state, status) pair is revision-desynced and must not be
|
|
20
|
+
* combined into one decision.
|
|
21
|
+
* @param {{ materializedRevision?: number|null }|null|undefined} state ViewState as read from state.json.
|
|
22
|
+
* @param {{ materializedRevision?: number|null }|null|undefined} status RunStatus as read from status.json.
|
|
23
|
+
* @returns {boolean}
|
|
24
|
+
*/
|
|
25
|
+
export function statusRevisionDesynced(state, status) {
|
|
26
|
+
if (state?.materializedRevision == null || status?.materializedRevision == null) return false;
|
|
27
|
+
return state.materializedRevision !== status.materializedRevision;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Re-read BOTH halves of the pair fresh and re-check the desync verdict.
|
|
32
|
+
*
|
|
33
|
+
* TOCTOU guard for long-lived readers (reconcile iterates a listRows snapshot;
|
|
34
|
+
* earlier rows' awaited commands let the on-disk pair advance past it): a
|
|
35
|
+
* suspicion raised against the snapshot must be confirmed against fresh reads
|
|
36
|
+
* before acting on it. Under the coordinator's view-lock pairing invariant
|
|
37
|
+
* both files move together, so a fresh consistent pair clears the suspicion;
|
|
38
|
+
* the residual window between the two fresh reads is sub-ms.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} root
|
|
41
|
+
* @param {string} viewId
|
|
42
|
+
* @param {string|null} runId
|
|
43
|
+
* @param {{ readState?: typeof readStateImpl, readStatus?: typeof readStatusImpl }} [readers] injection for tests.
|
|
44
|
+
* @returns {{ state: object|null, status: object|null, desynced: boolean }}
|
|
45
|
+
*/
|
|
46
|
+
export function rereadPair(root, viewId, runId, readers = {}) {
|
|
47
|
+
const readState = readers.readState ?? readStateImpl;
|
|
48
|
+
const readStatus = readers.readStatus ?? readStatusImpl;
|
|
49
|
+
const state = readState(root, viewId);
|
|
50
|
+
const status = readStatus(root, viewId, runId);
|
|
51
|
+
return { state, status, desynced: statusRevisionDesynced(state, status) };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Per-view episode throttle for desync reporting (issue #111 CR r1).
|
|
56
|
+
*
|
|
57
|
+
* A desync is a persistent CONDITION, not a one-shot event: reconcile runs on
|
|
58
|
+
* every dashboard poll (~700ms), and for unrepairable pairs (a torn transient
|
|
59
|
+
* beat has no journal record; or the coordinator is off) the condition never
|
|
60
|
+
* clears. Logging unthrottled would grow diagnostics.jsonl without bound.
|
|
61
|
+
* The throttle logs each distinct (stateRev:statusRev) pair once per viewer;
|
|
62
|
+
* a changed pair (progression, or a fresh crash at new revisions) logs again,
|
|
63
|
+
* and a repaired pair simply stops firing — no reset bookkeeping needed.
|
|
64
|
+
*
|
|
65
|
+
* Kick-retry tracking (issue #111 CR r2): a failed repair kick must not
|
|
66
|
+
* consume the recovery path — an idle dashboard would otherwise never run
|
|
67
|
+
* boot replay for a repairable pair. `markKickFailed`/`shouldRetryKick`
|
|
68
|
+
* track that independently of the diagnostic episode, so retries stay
|
|
69
|
+
* unbounded while the kick keeps failing while diagnostics stay once per
|
|
70
|
+
* episode. A successful kick clears the flag (the coordinator is up: boot
|
|
71
|
+
* replay already repaired a repairable pair; an unrepairable-torn pair
|
|
72
|
+
* gains nothing from re-kicking).
|
|
73
|
+
*
|
|
74
|
+
* @returns {{ shouldLog(viewId: string, stateRevision: number|null, statusRevision: number|null): boolean, markKickFailed(viewId: string): void, shouldRetryKick(viewId: string): boolean, clearKickFailed(viewId: string): void }}
|
|
75
|
+
*/
|
|
76
|
+
export function createDesyncEpisodeThrottle() {
|
|
77
|
+
/** @type {Map<string, string>} */
|
|
78
|
+
const lastLogged = new Map();
|
|
79
|
+
/** @type {Set<string>} */
|
|
80
|
+
const kickFailed = new Set();
|
|
81
|
+
return {
|
|
82
|
+
shouldLog(viewId, stateRevision, statusRevision) {
|
|
83
|
+
const pair = `${stateRevision ?? "null"}:${statusRevision ?? "null"}`;
|
|
84
|
+
if (lastLogged.get(viewId) === pair) return false;
|
|
85
|
+
lastLogged.set(viewId, pair);
|
|
86
|
+
return true;
|
|
87
|
+
},
|
|
88
|
+
markKickFailed(viewId) {
|
|
89
|
+
kickFailed.add(viewId);
|
|
90
|
+
},
|
|
91
|
+
shouldRetryKick(viewId) {
|
|
92
|
+
return kickFailed.has(viewId);
|
|
93
|
+
},
|
|
94
|
+
clearKickFailed(viewId) {
|
|
95
|
+
kickFailed.delete(viewId);
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Prompt builders for steering-first Agent Board workflows. */
|
|
2
|
+
|
|
3
|
+
/** @param {string} userText */
|
|
4
|
+
export function buildPlanRequestPrompt(userText = "") {
|
|
5
|
+
const extra = String(userText || "").trim();
|
|
6
|
+
return [
|
|
7
|
+
"Create an implementation plan only. Do not modify files unless explicitly instructed by the user.",
|
|
8
|
+
"Return: scope, assumptions, proposed steps, validation plan, risks, and open questions.",
|
|
9
|
+
"This Agent Board plan mode is advisory; hard tool restrictions may not be enforced by the runtime yet.",
|
|
10
|
+
extra ? `User request/context: ${extra}` : "Use the current session context and repository state.",
|
|
11
|
+
].join("\n\n");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** @param {string} planText */
|
|
15
|
+
export function buildApprovePlanPrompt(planText = "") {
|
|
16
|
+
return [
|
|
17
|
+
"The user approved the plan below. Implement it now.",
|
|
18
|
+
"When finished, summarize changed files, commands/tests run, validation results, and residual risks for the Agent Board evidence panel.",
|
|
19
|
+
"Plan:",
|
|
20
|
+
String(planText || "(no plan text captured)"),
|
|
21
|
+
].join("\n\n");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** @param {string} planText @param {string} feedback */
|
|
25
|
+
export function buildPlanChangesPrompt(planText = "", feedback = "") {
|
|
26
|
+
return [
|
|
27
|
+
"Revise the implementation plan based on the user's feedback. Do not implement yet.",
|
|
28
|
+
"Return the revised plan and call out what changed.",
|
|
29
|
+
"Current plan:",
|
|
30
|
+
String(planText || "(no prior plan captured)"),
|
|
31
|
+
"User feedback:",
|
|
32
|
+
String(feedback || "(no feedback supplied)"),
|
|
33
|
+
].join("\n\n");
|
|
34
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/** Durable steering / plan-approval state helpers. */
|
|
2
|
+
import { atomicWriteJson, readJson } from "./atomic.mjs";
|
|
3
|
+
import { truncate } from "./heuristics.mjs";
|
|
4
|
+
import * as P from "./paths.mjs";
|
|
5
|
+
|
|
6
|
+
const STATES = new Set(["none", "plan_requested", "awaiting_approval", "approved", "changes_requested", "executing_approved_plan"]);
|
|
7
|
+
|
|
8
|
+
/** @param {string} viewId @param {number} [now] @returns {import("./types.mjs").SteeringState} */
|
|
9
|
+
export function emptySteeringState(viewId, now = Date.now()) {
|
|
10
|
+
return {
|
|
11
|
+
version: 1,
|
|
12
|
+
viewId,
|
|
13
|
+
status: "none",
|
|
14
|
+
updatedAt: now,
|
|
15
|
+
planText: "",
|
|
16
|
+
planRunId: null,
|
|
17
|
+
approvedAt: null,
|
|
18
|
+
changeRequest: null,
|
|
19
|
+
executionRunId: null,
|
|
20
|
+
history: [],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** @param {string} root @param {string} viewId */
|
|
25
|
+
export function readSteering(root, viewId) {
|
|
26
|
+
return normalizeSteering(readJson(P.steeringPath(root, viewId), null), viewId);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** @param {string} root @param {import("./types.mjs").SteeringState} state */
|
|
30
|
+
export function writeSteering(root, state) {
|
|
31
|
+
const normalized = normalizeSteering(state, state.viewId);
|
|
32
|
+
normalized.updatedAt = Date.now();
|
|
33
|
+
atomicWriteJson(P.steeringPath(root, normalized.viewId), normalized);
|
|
34
|
+
return normalized;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** @param {import("./types.mjs").SteeringState} state @returns {import("./types.mjs").SteeringSummary} */
|
|
38
|
+
export function summarizeSteering(state) {
|
|
39
|
+
const s = normalizeSteering(state, state?.viewId ?? "");
|
|
40
|
+
return {
|
|
41
|
+
status: s.status,
|
|
42
|
+
awaitingApproval: s.status === "awaiting_approval",
|
|
43
|
+
planPreview: s.planText ? truncate(s.planText.replace(/\s+/g, " ").trim(), 180) : null,
|
|
44
|
+
updatedAt: s.updatedAt ?? null,
|
|
45
|
+
question: s.status === "awaiting_approval" ? "Approve this plan?" : null,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** @param {string} root @param {string} viewId @param {{ runId?: string|null, note?: string }} [opts] */
|
|
50
|
+
export function requestPlan(root, viewId, opts = {}) {
|
|
51
|
+
const state = readSteering(root, viewId);
|
|
52
|
+
transition(state, "plan_requested", "request_plan", opts.runId ?? null, opts.note ?? null);
|
|
53
|
+
state.planRunId = opts.runId ?? state.planRunId ?? null;
|
|
54
|
+
return writeSteering(root, state);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** @param {string} root @param {string} viewId @param {{ runId?: string|null, planText: string, note?: string }} opts */
|
|
58
|
+
export function recordPlanReady(root, viewId, opts) {
|
|
59
|
+
const state = readSteering(root, viewId);
|
|
60
|
+
state.planText = String(opts.planText || "").trim();
|
|
61
|
+
state.planRunId = opts.runId ?? state.planRunId ?? null;
|
|
62
|
+
transition(state, "awaiting_approval", "plan_ready", opts.runId ?? null, opts.note ?? null);
|
|
63
|
+
return writeSteering(root, state);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** @param {string} root @param {string} viewId @param {{ runId?: string|null, note?: string }} [opts] */
|
|
67
|
+
export function approvePlan(root, viewId, opts = {}) {
|
|
68
|
+
const state = readSteering(root, viewId);
|
|
69
|
+
if (state.status !== "awaiting_approval") return { ok: false, error: "No plan is awaiting approval", state };
|
|
70
|
+
state.approvedAt = Date.now();
|
|
71
|
+
state.executionRunId = opts.runId ?? null;
|
|
72
|
+
transition(state, "approved", "approve_plan", opts.runId ?? null, opts.note ?? null);
|
|
73
|
+
const saved = writeSteering(root, state);
|
|
74
|
+
return { ok: true, state: saved };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** @param {string} root @param {string} viewId @param {string} feedback @param {{ runId?: string|null }} [opts] */
|
|
78
|
+
export function requestPlanChanges(root, viewId, feedback, opts = {}) {
|
|
79
|
+
const clean = String(feedback || "").trim();
|
|
80
|
+
if (!clean) return { ok: false, error: "Empty change request", state: readSteering(root, viewId) };
|
|
81
|
+
const state = readSteering(root, viewId);
|
|
82
|
+
state.changeRequest = clean;
|
|
83
|
+
transition(state, "changes_requested", "request_plan_changes", opts.runId ?? null, clean);
|
|
84
|
+
const saved = writeSteering(root, state);
|
|
85
|
+
return { ok: true, state: saved };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** @param {string} root @param {string} viewId @param {{ runId?: string|null }} [opts] */
|
|
89
|
+
export function markExecutingApprovedPlan(root, viewId, opts = {}) {
|
|
90
|
+
const state = readSteering(root, viewId);
|
|
91
|
+
state.executionRunId = opts.runId ?? state.executionRunId ?? null;
|
|
92
|
+
transition(state, "executing_approved_plan", "execute_approved_plan", opts.runId ?? null, null);
|
|
93
|
+
return writeSteering(root, state);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** @param {string} root @param {string} viewId @param {{ note?: string }} [opts] */
|
|
97
|
+
export function resetSteering(root, viewId, opts = {}) {
|
|
98
|
+
const state = readSteering(root, viewId);
|
|
99
|
+
transition(state, "none", "reset", null, opts.note ?? null);
|
|
100
|
+
state.planText = "";
|
|
101
|
+
state.planRunId = null;
|
|
102
|
+
state.approvedAt = null;
|
|
103
|
+
state.changeRequest = null;
|
|
104
|
+
state.executionRunId = null;
|
|
105
|
+
return writeSteering(root, state);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** @param {any} state @param {string} viewId @returns {import("./types.mjs").SteeringState} */
|
|
109
|
+
function normalizeSteering(state, viewId) {
|
|
110
|
+
const base = emptySteeringState(viewId || state?.viewId || "");
|
|
111
|
+
if (!state || typeof state !== "object") return base;
|
|
112
|
+
return {
|
|
113
|
+
...base,
|
|
114
|
+
...state,
|
|
115
|
+
viewId: typeof state.viewId === "string" ? state.viewId : base.viewId,
|
|
116
|
+
status: STATES.has(state.status) ? state.status : "none",
|
|
117
|
+
planText: typeof state.planText === "string" ? state.planText : "",
|
|
118
|
+
planRunId: typeof state.planRunId === "string" ? state.planRunId : null,
|
|
119
|
+
approvedAt: Number.isFinite(state.approvedAt) ? state.approvedAt : null,
|
|
120
|
+
changeRequest: typeof state.changeRequest === "string" ? state.changeRequest : null,
|
|
121
|
+
executionRunId: typeof state.executionRunId === "string" ? state.executionRunId : null,
|
|
122
|
+
history: Array.isArray(state.history) ? state.history : [],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** @param {import("./types.mjs").SteeringState} state @param {import("./types.mjs").SteeringModeState} to @param {string} action @param {string|null} runId @param {string|null} note */
|
|
127
|
+
function transition(state, to, action, runId, note) {
|
|
128
|
+
const now = Date.now();
|
|
129
|
+
const from = state.status;
|
|
130
|
+
state.status = to;
|
|
131
|
+
state.updatedAt = now;
|
|
132
|
+
state.history.push({ at: now, from, to, action, runId, note: note ? truncate(note, 240) : null });
|
|
133
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal stop-intent latch for detached runners (issue #153).
|
|
3
|
+
*
|
|
4
|
+
* A runner installs signal handlers as early as module scope — before its
|
|
5
|
+
* config is even read — but the real stop path (kill worker, finalize the
|
|
6
|
+
* run) only exists once the worker is spawned and the close handlers are
|
|
7
|
+
* wired. A stop signal that lands in between must not die with Node's
|
|
8
|
+
* default action (that would leave the run without a terminal state); the
|
|
9
|
+
* latch records it and the runner replays it once the real stop path exists.
|
|
10
|
+
*
|
|
11
|
+
* Pure state: no signal wiring, no timers, no I/O.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @returns {{
|
|
16
|
+
* note(signal: string): void,
|
|
17
|
+
* pending(): string | null,
|
|
18
|
+
* take(): string | null,
|
|
19
|
+
* }} A latch object. `note` is idempotent (the first signal wins, later ones
|
|
20
|
+
* collapse); `take` returns the recorded signal once, then null.
|
|
21
|
+
*/
|
|
22
|
+
export function createStopLatch() {
|
|
23
|
+
/** @type {string | null} */
|
|
24
|
+
let noted = null;
|
|
25
|
+
return {
|
|
26
|
+
/** Record a stop signal. @param {string} signal */
|
|
27
|
+
note(signal) {
|
|
28
|
+
if (noted == null) noted = signal;
|
|
29
|
+
},
|
|
30
|
+
/** The first recorded signal, or null. */
|
|
31
|
+
pending() {
|
|
32
|
+
return noted;
|
|
33
|
+
},
|
|
34
|
+
/** Read-and-clear: returns the recorded signal once, then null. */
|
|
35
|
+
take() {
|
|
36
|
+
const signal = noted;
|
|
37
|
+
noted = null;
|
|
38
|
+
return signal;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|