@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,414 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared data shapes for the agent-board store. JSDoc typedefs (consumed by the TS
|
|
3
|
+
* extension via `allowJs`) plus the canonical state vocabularies as runtime constants.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Semantic (task) state of a row. @typedef {"queued"|"working"|"needs_input"|"idle"|"holding"|"completed"|"failed"|"stopped"} SemanticState */
|
|
7
|
+
/** Process/liveness state. @typedef {"alive"|"exited"} ProcessState */
|
|
8
|
+
/** PTY host mode. @typedef {"json-runner"|"pty"} HostMode */
|
|
9
|
+
/** PTY host liveness. @typedef {"starting"|"alive"|"stopping"|"exited"|"failed"} HostState */
|
|
10
|
+
/** How a run was kicked off. @typedef {"dispatch"|"reply"|"plan"|"plan_change"|"plan_approval"} RunKind */
|
|
11
|
+
/** Worktree isolation mode for a row. @typedef {"off"|"worktree"} WorktreeMode */
|
|
12
|
+
/** Diagnostic severity. @typedef {"info"|"warn"|"error"} DiagnosticLevel */
|
|
13
|
+
/** Diagnostic event source. @typedef {"runner"|"host"|"service"|"queue"|"steering"|"evidence"|"store"} DiagnosticSource */
|
|
14
|
+
/** Evidence outcome. @typedef {"unknown"|"in_progress"|"ready"|"needs_input"|"failed"|"stopped"|"queued"|"working"|"idle"|"holding"|"completed"} EvidenceOutcome */
|
|
15
|
+
/** Command kind. @typedef {"test"|"build"|"lint"|"git"|"install"|"other"} EvidenceCommandKind */
|
|
16
|
+
/** Command status. @typedef {"started"|"passed"|"failed"|"unknown"} EvidenceCommandStatus */
|
|
17
|
+
/** File change action. @typedef {"edited"|"written"|"deleted"|"unknown"} EvidenceFileAction */
|
|
18
|
+
/** Follow-up kind. @typedef {"reply"|"plan_request"|"plan_approval"|"plan_change"} FollowUpKind */
|
|
19
|
+
/** Follow-up status. @typedef {"queued"|"claimed"|"completed"|"failed"|"cancelled"} FollowUpStatus */
|
|
20
|
+
/** Steering state. @typedef {"none"|"plan_requested"|"awaiting_approval"|"approved"|"changes_requested"|"executing_approved_plan"} SteeringModeState */
|
|
21
|
+
/** Dashboard auto-state bucket. @typedef {"needs_input"|"in_progress"|"done"} AutoStateKind */
|
|
22
|
+
/** Auto-state classifier source. @typedef {"model"|"heuristic"} AutoStateSource */
|
|
23
|
+
/** Auto-state classifier confidence. @typedef {"high"|"medium"|"low"} AutoStateConfidence */
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* LLM/heuristic classification of the latest assistant turn.
|
|
27
|
+
* @typedef {Object} AutoStateClassification
|
|
28
|
+
* @property {number} version
|
|
29
|
+
* @property {AutoStateKind} kind
|
|
30
|
+
* @property {SemanticState} semanticState
|
|
31
|
+
* @property {AutoStateConfidence} confidence
|
|
32
|
+
* @property {AutoStateSource} source
|
|
33
|
+
* @property {string} reason
|
|
34
|
+
* @property {string|null} question
|
|
35
|
+
* @property {number} classifiedAt
|
|
36
|
+
* @property {number|null} lastAgentActivityAt
|
|
37
|
+
* @property {string} textHash
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
export const SEMANTIC_STATES = /** @type {const} */ ([
|
|
41
|
+
"queued",
|
|
42
|
+
"working",
|
|
43
|
+
"needs_input",
|
|
44
|
+
"idle",
|
|
45
|
+
"holding",
|
|
46
|
+
"completed",
|
|
47
|
+
"failed",
|
|
48
|
+
"stopped",
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
export const PROCESS_STATES = /** @type {const} */ (["alive", "exited"]);
|
|
52
|
+
|
|
53
|
+
/** Order rows are grouped/shown in the dashboard (most-actionable first). */
|
|
54
|
+
export const GROUP_ORDER = /** @type {const} */ ([
|
|
55
|
+
"queued",
|
|
56
|
+
"working",
|
|
57
|
+
"needs_input",
|
|
58
|
+
"idle",
|
|
59
|
+
"holding",
|
|
60
|
+
"completed",
|
|
61
|
+
"failed",
|
|
62
|
+
"stopped",
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
/** Human labels for group headers. @type {Record<SemanticState,string>} */
|
|
66
|
+
export const GROUP_LABELS = {
|
|
67
|
+
needs_input: "Needs answer",
|
|
68
|
+
working: "Running",
|
|
69
|
+
queued: "Queued",
|
|
70
|
+
failed: "Failed",
|
|
71
|
+
completed: "Done",
|
|
72
|
+
idle: "Needs instructions",
|
|
73
|
+
holding: "On hold",
|
|
74
|
+
stopped: "Stopped",
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Stable row metadata (`meta.json`).
|
|
79
|
+
* @typedef {Object} ViewMeta
|
|
80
|
+
* @property {number} version
|
|
81
|
+
* @property {string} id
|
|
82
|
+
* @property {string} name
|
|
83
|
+
* @property {string} cwd Working dir the worker runs in (repo or worktree path).
|
|
84
|
+
* @property {string} repoCwd Original repo dir requested at dispatch (== cwd unless worktree).
|
|
85
|
+
* @property {string|null} repoRoot Git repo root, if any.
|
|
86
|
+
* @property {string} sessionFile Managed Pi session JSONL path.
|
|
87
|
+
* @property {number} createdAt
|
|
88
|
+
* @property {number} updatedAt
|
|
89
|
+
* @property {boolean} pinned
|
|
90
|
+
* @property {"pi-session"} kind
|
|
91
|
+
* @property {string|null} defaultModel
|
|
92
|
+
* @property {"off"|"minimal"|"low"|"medium"|"high"|"xhigh"|null} defaultThinking
|
|
93
|
+
* @property {WorktreeMode} worktreeMode
|
|
94
|
+
* @property {string|null} worktreePath
|
|
95
|
+
* @property {boolean} writeCapable Whether this session may mutate files (default true).
|
|
96
|
+
* @property {boolean} archived Soft-deleted from the dashboard (data preserved).
|
|
97
|
+
* @property {"agent-board"} source
|
|
98
|
+
*/
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Derived dashboard state for a row (`state.json`).
|
|
102
|
+
* @typedef {Object} ViewState
|
|
103
|
+
* @property {number} version
|
|
104
|
+
* @property {string} viewId
|
|
105
|
+
* @property {string|null} currentRunId
|
|
106
|
+
* @property {SemanticState} semanticState
|
|
107
|
+
* @property {ProcessState} processState
|
|
108
|
+
* @property {string} summary
|
|
109
|
+
* @property {number} lastActivityAt
|
|
110
|
+
* @property {number} updatedAt
|
|
111
|
+
* @property {boolean} needsInput
|
|
112
|
+
* @property {boolean} hasError
|
|
113
|
+
* @property {string} latestAssistantPreview
|
|
114
|
+
* @property {{name:string, path:string|null}|null} latestTool
|
|
115
|
+
* @property {string|null} question
|
|
116
|
+
* @property {Array<{toolCallId:string, question:string}>} pendingQuestions
|
|
117
|
+
* @property {string|null} error
|
|
118
|
+
* @property {number|null} lastVisitedAt
|
|
119
|
+
* @property {number|null} lastAgentActivityAt Timestamp of the latest assistant reply shown in the row.
|
|
120
|
+
* @property {ReviewSummary} [review] Compact collected-evidence summary.
|
|
121
|
+
* @property {DiagnosticSummary} [diagnostics] Compact diagnostics summary.
|
|
122
|
+
* @property {FollowUpSummary} [followUps] Compact queued follow-up summary.
|
|
123
|
+
* @property {SteeringSummary} [steering] Compact plan/approval steering summary.
|
|
124
|
+
* @property {CodeRefsSummary} [codeRefs] Compact issue/PR reference summary.
|
|
125
|
+
* @property {AutoStateClassification|null} [autoState] Latest automatic terminal-state classification.
|
|
126
|
+
* @property {number} [materializedRevision] Coordinator-stamped monotonic revision (issue #91). Absent on legacy rows until the View State Coordinator first materializes the view.
|
|
127
|
+
*/
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Durable PTY host snapshot (`host.json`).
|
|
131
|
+
* @typedef {Object} HostStatus
|
|
132
|
+
* @property {number} version
|
|
133
|
+
* @property {string} viewId
|
|
134
|
+
* @property {HostMode} mode
|
|
135
|
+
* @property {number|null} runnerPid
|
|
136
|
+
* @property {number|null} childPid
|
|
137
|
+
* @property {string|null} socketPath
|
|
138
|
+
* @property {HostState} state
|
|
139
|
+
* @property {number} startedAt
|
|
140
|
+
* @property {number} lastSeenAt
|
|
141
|
+
* @property {number|null} endedAt
|
|
142
|
+
* @property {number|null} exitCode
|
|
143
|
+
* @property {string|null} error
|
|
144
|
+
* @property {number} cols
|
|
145
|
+
* @property {number} rows
|
|
146
|
+
* @property {number} attachedClients
|
|
147
|
+
* @property {boolean} [attachedEver] Whether any client attached to this host.
|
|
148
|
+
* @property {string|null} [instanceId] Owner/fencing token for this host launch (issue #70).
|
|
149
|
+
* @property {string|null} [configPath] Per-instance config file this host was launched with.
|
|
150
|
+
* @property {number|null} [claimAt] When the provisional claim was written (epoch ms).
|
|
151
|
+
* @property {number|null} [claimPid] PID of the service process that wrote the claim.
|
|
152
|
+
* @property {{pid:number,startToken:string|null}|null} [claimIdentity] Launch-time identity of the claiming service process.
|
|
153
|
+
* @property {{pid:number,startToken:string|null}|null} [runnerIdentity] Stable identity of the runner process.
|
|
154
|
+
* @property {number|null} [runnerSpawnedAt] When the runner spawn was confirmed (null = not yet spawned).
|
|
155
|
+
* @property {{pid:number,startToken:string|null}|null} [childIdentity] Stable identity of the child Pi process.
|
|
156
|
+
* @property {number|null} [childSpawnedAt] When the child spawn was confirmed (null = not yet spawned).
|
|
157
|
+
* @property {number|null} [readyAt] Set only after endpoint bind + child creation; alive implies readyAt != null.
|
|
158
|
+
* @property {number|null} [stopRequestedAt] When a revoke was requested via the host record.
|
|
159
|
+
* @property {string|null} [revokeToken] Random token written by requestHostStop to fence duplicate revokes.
|
|
160
|
+
* @property {string|null} [stopReason] Human-readable reason recorded with the revoke.
|
|
161
|
+
*/
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Durable per-run snapshot (`runs/<runId>/status.json`).
|
|
165
|
+
* @typedef {Object} RunStatus
|
|
166
|
+
* @property {number} version
|
|
167
|
+
* @property {string} runId
|
|
168
|
+
* @property {string} viewId
|
|
169
|
+
* @property {number|null} pid
|
|
170
|
+
* @property {number} startedAt
|
|
171
|
+
* @property {number|null} endedAt
|
|
172
|
+
* @property {number|null} exitCode
|
|
173
|
+
* @property {RunKind} kind
|
|
174
|
+
* @property {string} prompt
|
|
175
|
+
* @property {string|null} model
|
|
176
|
+
* @property {SemanticState} semanticState
|
|
177
|
+
* @property {ProcessState} processState
|
|
178
|
+
* @property {string} summary
|
|
179
|
+
* @property {number} lastActivityAt
|
|
180
|
+
* @property {{name:string, path:string|null}|null} currentTool
|
|
181
|
+
* @property {string} latestAssistantPreview
|
|
182
|
+
* @property {string|null} question
|
|
183
|
+
* @property {Array<{toolCallId:string, question:string}>} pendingQuestions
|
|
184
|
+
* @property {string|null} error
|
|
185
|
+
* @property {number|null} lastAgentActivityAt Timestamp of the latest assistant reply.
|
|
186
|
+
* @property {string|null} stopReason
|
|
187
|
+
* @property {boolean} stoppedByUser
|
|
188
|
+
* @property {number} turns
|
|
189
|
+
* @property {number} toolCount
|
|
190
|
+
* @property {number} [eventCount]
|
|
191
|
+
* @property {number|null} [lastEventAt]
|
|
192
|
+
* @property {number} [updatedAt] In-memory foreground rebuild freshness (issue #225); not persisted on run status files.
|
|
193
|
+
* @property {EvidenceUsage|null} [usage]
|
|
194
|
+
* @property {string|null} [stallReason]
|
|
195
|
+
* @property {ReviewSummary|null} [evidenceSummary]
|
|
196
|
+
* @property {number} [materializedRevision] Coordinator-stamped monotonic revision shared with the view's state.json (issue #91). Absent on legacy rows until first coordinator materialization.
|
|
197
|
+
* @property {AutoStateClassification|null} [autoState]
|
|
198
|
+
*/
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Configuration handed to the detached runner (written to the run dir as `config.json`
|
|
202
|
+
* and passed by path on argv).
|
|
203
|
+
* @typedef {Object} RunConfig
|
|
204
|
+
* @property {string} root
|
|
205
|
+
* @property {string} viewId
|
|
206
|
+
* @property {string} runId
|
|
207
|
+
* @property {RunKind} kind
|
|
208
|
+
* @property {string} sessionFile
|
|
209
|
+
* @property {string} cwd
|
|
210
|
+
* @property {string} prompt
|
|
211
|
+
* @property {string} piCommand Executable to launch the worker (e.g. "pi" or a node path).
|
|
212
|
+
* @property {string[]} piArgsPrefix Args before our flags (e.g. [cliJsPath] when piCommand is node).
|
|
213
|
+
* @property {string|null} model
|
|
214
|
+
* @property {"off"|"minimal"|"low"|"medium"|"high"|"xhigh"|null} thinkingLevel
|
|
215
|
+
* @property {string|null} tools
|
|
216
|
+
*/
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Configuration handed to the detached PTY host runner (written as `host-config.json`).
|
|
220
|
+
* @typedef {Object} HostConfig
|
|
221
|
+
* @property {string} root
|
|
222
|
+
* @property {string} viewId
|
|
223
|
+
* @property {string} sessionFile
|
|
224
|
+
* @property {string} cwd
|
|
225
|
+
* @property {string|null} initialPrompt
|
|
226
|
+
* @property {string} piCommand
|
|
227
|
+
* @property {string[]} piArgsPrefix
|
|
228
|
+
* @property {string|null} model
|
|
229
|
+
* @property {"off"|"minimal"|"low"|"medium"|"high"|"xhigh"|null} thinkingLevel
|
|
230
|
+
* @property {string|null} tools
|
|
231
|
+
* @property {Record<string,string>} env
|
|
232
|
+
* @property {number} cols
|
|
233
|
+
* @property {number} rows
|
|
234
|
+
* @property {number|null} screenLogMaxBytes per-view screen.log write cap; null = runner default
|
|
235
|
+
* @property {string|null} [instanceId] Owner/fencing token for this host launch (issue #70).
|
|
236
|
+
* @property {string|null} [configPath] Per-instance config file path; each launch owns its own so concurrent launches never share a config.
|
|
237
|
+
* @property {string|null} [socketPath] Per-instance control endpoint the spawned runner must bind.
|
|
238
|
+
*/
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Configuration handed to the detached title runner (written as `title-config.json`).
|
|
242
|
+
* @typedef {Object} TitleConfig
|
|
243
|
+
* @property {string} root
|
|
244
|
+
* @property {string} viewId
|
|
245
|
+
* @property {string} cwd
|
|
246
|
+
* @property {string} prompt
|
|
247
|
+
* @property {string} fallbackName
|
|
248
|
+
* @property {string} piCommand
|
|
249
|
+
* @property {string[]} piArgsPrefix
|
|
250
|
+
* @property {string|null} model
|
|
251
|
+
*/
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Configuration handed to the detached auto-state runner.
|
|
255
|
+
* @typedef {Object} AutoStateConfig
|
|
256
|
+
* @property {string} root
|
|
257
|
+
* @property {string} viewId
|
|
258
|
+
* @property {string|null} runId
|
|
259
|
+
* @property {string} cwd
|
|
260
|
+
* @property {string} piCommand
|
|
261
|
+
* @property {string[]} piArgsPrefix
|
|
262
|
+
*/
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* @typedef {Object} DiagnosticEvent
|
|
266
|
+
* @property {number} version
|
|
267
|
+
* @property {number} at
|
|
268
|
+
* @property {string} viewId
|
|
269
|
+
* @property {string|null} runId
|
|
270
|
+
* @property {DiagnosticSource|string} source
|
|
271
|
+
* @property {DiagnosticLevel} level
|
|
272
|
+
* @property {string} code
|
|
273
|
+
* @property {string} message
|
|
274
|
+
* @property {Record<string,any>} details
|
|
275
|
+
*/
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* @typedef {Object} DiagnosticSummary
|
|
279
|
+
* @property {number} count
|
|
280
|
+
* @property {number} warningCount
|
|
281
|
+
* @property {number} errorCount
|
|
282
|
+
* @property {number|null} lastAt
|
|
283
|
+
* @property {DiagnosticLevel|null} lastLevel
|
|
284
|
+
* @property {string|null} lastCode
|
|
285
|
+
* @property {string|null} lastMessage
|
|
286
|
+
* @property {boolean} stalled
|
|
287
|
+
* @property {string|null} stallReason
|
|
288
|
+
*/
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* @typedef {Object} EvidenceUsage
|
|
292
|
+
* @property {number} inputTokens
|
|
293
|
+
* @property {number} outputTokens
|
|
294
|
+
* @property {number} totalTokens
|
|
295
|
+
*/
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* @typedef {Object} EvidenceCommand
|
|
299
|
+
* @property {string} id
|
|
300
|
+
* @property {number} at
|
|
301
|
+
* @property {string} command
|
|
302
|
+
* @property {EvidenceCommandKind} kind
|
|
303
|
+
* @property {EvidenceCommandStatus} status
|
|
304
|
+
* @property {number|null} exitCode
|
|
305
|
+
* @property {number|null} durationMs
|
|
306
|
+
* @property {string} outputPreview
|
|
307
|
+
*/
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* @typedef {Object} EvidenceFileChange
|
|
311
|
+
* @property {string} path
|
|
312
|
+
* @property {EvidenceFileAction} action
|
|
313
|
+
* @property {string|null} toolName
|
|
314
|
+
* @property {number} firstSeenAt
|
|
315
|
+
* @property {number} lastSeenAt
|
|
316
|
+
* @property {number} count
|
|
317
|
+
*/
|
|
318
|
+
|
|
319
|
+
/** @typedef {{ at:number, source:string, message:string, toolName:string|null }} EvidenceError */
|
|
320
|
+
/** @typedef {{ at:number, text:string }} EvidenceAssistantClaim */
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* @typedef {Object} EvidenceSnapshot
|
|
324
|
+
* @property {number} version
|
|
325
|
+
* @property {string} viewId
|
|
326
|
+
* @property {string|null} runId
|
|
327
|
+
* @property {number} updatedAt
|
|
328
|
+
* @property {EvidenceOutcome|string} outcome
|
|
329
|
+
* @property {boolean} ready
|
|
330
|
+
* @property {string} summary
|
|
331
|
+
* @property {EvidenceCommand[]} commands
|
|
332
|
+
* @property {EvidenceFileChange[]} fileChanges
|
|
333
|
+
* @property {EvidenceError[]} errors
|
|
334
|
+
* @property {EvidenceAssistantClaim[]} assistantEvidence
|
|
335
|
+
* @property {EvidenceUsage|null} usage
|
|
336
|
+
* @property {number} [eventCount]
|
|
337
|
+
* @property {string} [source]
|
|
338
|
+
*/
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* @typedef {Object} ReviewSummary
|
|
342
|
+
* @property {boolean} ready
|
|
343
|
+
* @property {EvidenceOutcome|string} outcome
|
|
344
|
+
* @property {number} fileChangeCount
|
|
345
|
+
* @property {number} commandCount
|
|
346
|
+
* @property {number} failedCommandCount
|
|
347
|
+
* @property {number} errorCount
|
|
348
|
+
* @property {string} latestAssistantEvidence
|
|
349
|
+
* @property {number|null} updatedAt
|
|
350
|
+
*/
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* @typedef {Object} FollowUpItem
|
|
354
|
+
* @property {string} id
|
|
355
|
+
* @property {number} seq
|
|
356
|
+
* @property {string} viewId
|
|
357
|
+
* @property {FollowUpKind} kind
|
|
358
|
+
* @property {string} text
|
|
359
|
+
* @property {number} createdAt
|
|
360
|
+
* @property {number} updatedAt
|
|
361
|
+
* @property {FollowUpStatus} status
|
|
362
|
+
* @property {string} source
|
|
363
|
+
* @property {"auto"|"now"|"queue"} delivery
|
|
364
|
+
* @property {string|null} runId
|
|
365
|
+
* @property {number|null} claimedAt
|
|
366
|
+
* @property {number|null} completedAt
|
|
367
|
+
* @property {number} attempts
|
|
368
|
+
* @property {string|null} error
|
|
369
|
+
*/
|
|
370
|
+
|
|
371
|
+
/** @typedef {{ version:number, viewId:string, nextSeq:number, updatedAt:number, items:FollowUpItem[] }} FollowUpQueue */
|
|
372
|
+
/** @typedef {{ queuedCount:number, claimedCount:number, lastQueuedAt:number|null, lastQueuedPreview:string|null }} FollowUpSummary */
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* @typedef {Object} SteeringState
|
|
376
|
+
* @property {number} version
|
|
377
|
+
* @property {string} viewId
|
|
378
|
+
* @property {SteeringModeState} status
|
|
379
|
+
* @property {number} updatedAt
|
|
380
|
+
* @property {string} planText
|
|
381
|
+
* @property {string|null} planRunId
|
|
382
|
+
* @property {number|null} approvedAt
|
|
383
|
+
* @property {string|null} changeRequest
|
|
384
|
+
* @property {string|null} executionRunId
|
|
385
|
+
* @property {Array<{at:number, from:string, to:string, action:string, runId:string|null, note:string|null}>} history
|
|
386
|
+
*/
|
|
387
|
+
|
|
388
|
+
/** @typedef {{ status:SteeringModeState, awaitingApproval:boolean, planPreview:string|null, updatedAt:number|null, question:string|null }} SteeringSummary */
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Compact issue/PR reference summary for a view (`github.json`).
|
|
392
|
+
* @typedef {Object} CodeRefsSummary
|
|
393
|
+
* @property {string|null} provider Resolved provider name (e.g. "github"); always non-null after a successful extraction (falls back to "generic"), null only before any extraction has run.
|
|
394
|
+
* @property {string} issuePrefix Issue-number prefix resolved from the matched provider at write time (default "#").
|
|
395
|
+
* @property {string} prPrefix PR/MR-number prefix resolved from the matched provider at write time (default "▸#"; "!" for gitlab).
|
|
396
|
+
* @property {import("./code-refs.mjs").Ref|null} issue
|
|
397
|
+
* @property {import("./code-refs.mjs").Ref|null} pr
|
|
398
|
+
* @property {import("./code-refs.mjs").Ref[]} allRefs Distinct refs for the peek view (max 10).
|
|
399
|
+
*/
|
|
400
|
+
|
|
401
|
+
/** Roster index (`roster.json`). @typedef {Object} Roster @property {number} version @property {string[]} views */
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Persisted launch dialog defaults (`launch-prefs.json`).
|
|
405
|
+
* @typedef {Object} LaunchPrefs
|
|
406
|
+
* @property {number} version
|
|
407
|
+
* @property {string|null} cwd
|
|
408
|
+
* @property {string|null} model
|
|
409
|
+
* @property {"off"|"minimal"|"low"|"medium"|"high"|"xhigh"|null} thinkingLevel
|
|
410
|
+
* @property {number|null} screenLogRetentionDays days before an ended view's screen.log is GC'd; 0 disables GC
|
|
411
|
+
* @property {number|null} screenLogMaxSize per-view screen.log write cap in bytes; null = built-in default
|
|
412
|
+
*/
|
|
413
|
+
|
|
414
|
+
export {};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Warm-host sweep reclaim (issue #75).
|
|
3
|
+
*
|
|
4
|
+
* Idle PTY hosts must not live forever: after detach the runner is a detached
|
|
5
|
+
* orphan that nothing else will reap (Windows has no parent-death cascade, and
|
|
6
|
+
* the dashboard host may exit without a cleanup hook). The design intent
|
|
7
|
+
* (AGENT_BOARD_WARM_HOST_TTL_MS / MAX_WARM_HOSTS) already exists in
|
|
8
|
+
* service.mjs pruneWarmHosts but only fires lazily on attach/prewarm/dispatch.
|
|
9
|
+
* This module extracts the pure eviction decision plus a periodic sweeper so
|
|
10
|
+
* the TTL actually runs.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** @param {import("./store.mjs").Row} row */
|
|
14
|
+
export function hasPendingQuestions(row) {
|
|
15
|
+
return Array.isArray(row.state?.pendingQuestions) && row.state.pendingQuestions.length > 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Busy = an agent run is active (queued/working) or the session is waiting on
|
|
20
|
+
* user questions. Idle/completed/failed/stopped are never busy.
|
|
21
|
+
* @param {import("./store.mjs").Row} row
|
|
22
|
+
*/
|
|
23
|
+
export function isAgentBusy(row) {
|
|
24
|
+
const st = row.state?.semanticState;
|
|
25
|
+
return Boolean(row.alive && (st === "queued" || st === "working" || hasPendingQuestions(row)));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Whether queued follow-ups may auto-drain for this row (issue #145).
|
|
30
|
+
* Deliberately an allow-list: a manual verdict state outside {idle, completed}
|
|
31
|
+
* (today: holding) must never be woken by the follow-up queue — only the user
|
|
32
|
+
* resumes it. Pure row predicate; co-located with isAgentBusy, which it
|
|
33
|
+
* composes, so both share this unit-test home.
|
|
34
|
+
* @param {import("./store.mjs").Row} row
|
|
35
|
+
*/
|
|
36
|
+
export function canAutoDrain(row) {
|
|
37
|
+
const st = row.state?.semanticState;
|
|
38
|
+
return !isAgentBusy(row) && (st === "idle" || st === "completed");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Pure eviction decision for warm PTY hosts. No IO, no env: every threshold is
|
|
43
|
+
* passed in so the logic is directly unit-testable.
|
|
44
|
+
*
|
|
45
|
+
* idle = host alive && !busy && no attached clients && not keepViewId.
|
|
46
|
+
* graceMs exempts freshly started hosts (attach handoff race: ensureHost has
|
|
47
|
+
* started a host but the client has not connected yet).
|
|
48
|
+
* isProtected is injected by the caller (this pure function never reads disk):
|
|
49
|
+
* a host running a background workflow is exempt from both eviction branches.
|
|
50
|
+
* TTL eviction runs first; survivors over maxWarm are evicted oldest-first by
|
|
51
|
+
* idleSince = max(state.lastActivityAt, host.startedAt) || meta.updatedAt (host-level
|
|
52
|
+
* idle counts from host start, so a stale lastActivityAt from a previous host
|
|
53
|
+
* incarnation never makes a fresh host look idle longer than it has been up).
|
|
54
|
+
*
|
|
55
|
+
* @param {Array<import("./store.mjs").Row>} rows
|
|
56
|
+
* @param {{ now: number, maxWarm: number, ttlMs: number, graceMs?: number, keepViewId?: string|null, isProtected?: ((row: import("./store.mjs").Row) => boolean)|null }} o
|
|
57
|
+
* @returns {{ ttlEvicted: string[], excessEvicted: string[] }} viewIds, ttl group first
|
|
58
|
+
*/
|
|
59
|
+
export function selectIdleHostsToEvict(rows, { now, maxWarm, ttlMs, graceMs = 0, keepViewId = null, isProtected = null }) {
|
|
60
|
+
// Both-zero = eviction disabled entirely. Callers (service.pruneWarmHosts)
|
|
61
|
+
// may additionally early-return for the same reason.
|
|
62
|
+
if (maxWarm === 0 && ttlMs === 0) return { ttlEvicted: [], excessEvicted: [] };
|
|
63
|
+
const idle = [];
|
|
64
|
+
for (const row of rows) {
|
|
65
|
+
if (keepViewId != null && row.meta.id === keepViewId) continue;
|
|
66
|
+
if (!row.hostAlive) continue;
|
|
67
|
+
if (isAgentBusy(row)) continue;
|
|
68
|
+
// Background-run protection (issue #230): a host executing a background
|
|
69
|
+
// workflow still counts as busy even though the assistant turn ended.
|
|
70
|
+
if (isProtected && isProtected(row)) continue;
|
|
71
|
+
if ((row.host?.attachedClients ?? 0) !== 0) continue;
|
|
72
|
+
const startedAt = row.host?.startedAt;
|
|
73
|
+
if (graceMs > 0 && startedAt != null && now - startedAt < graceMs) continue;
|
|
74
|
+
// Host-level idle counts from host start: a stale lastActivityAt from a
|
|
75
|
+
// previous host incarnation must not make a freshly prewarmed host look
|
|
76
|
+
// idle for longer than it has actually been up (issue #75 review #1).
|
|
77
|
+
const idleSince = Math.max(row.state?.lastActivityAt ?? 0, startedAt ?? 0) || row.meta.updatedAt;
|
|
78
|
+
idle.push({ id: row.meta.id, idleSince });
|
|
79
|
+
}
|
|
80
|
+
const ttlEvicted = [];
|
|
81
|
+
const survivors = [];
|
|
82
|
+
for (const it of idle) {
|
|
83
|
+
// Keep the historical semantics: ttlMs === 0 disables the ttl branch
|
|
84
|
+
// (only the maxWarm cap applies); both zero disables eviction entirely.
|
|
85
|
+
if (ttlMs > 0 && now - it.idleSince > ttlMs) ttlEvicted.push(it.id);
|
|
86
|
+
else survivors.push(it);
|
|
87
|
+
}
|
|
88
|
+
survivors.sort((a, b) => a.idleSince - b.idleSince);
|
|
89
|
+
const excess = Math.max(0, survivors.length - maxWarm);
|
|
90
|
+
return { ttlEvicted, excessEvicted: survivors.slice(0, excess).map((it) => it.id) };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Periodic sweeper for warm hosts. The interval timer is unref'd so it never
|
|
95
|
+
* holds the host pi's event loop open on exit. intervalMs <= 0 disables the
|
|
96
|
+
* periodic part; sweepNow() always works.
|
|
97
|
+
*
|
|
98
|
+
* stop() only stops the periodic timer: sweepNow() stays available afterwards
|
|
99
|
+
* (the lifecycle wiring calls sweepNow() again on shutdown/dispose). start()
|
|
100
|
+
* is idempotent and may restart the timer after a stop.
|
|
101
|
+
* @param {{ sweep: () => void, intervalMs: number }} o
|
|
102
|
+
*/
|
|
103
|
+
export function createWarmHostSweeper({ sweep, intervalMs }) {
|
|
104
|
+
let timer = null;
|
|
105
|
+
return {
|
|
106
|
+
active: true,
|
|
107
|
+
start() {
|
|
108
|
+
if (timer !== null) return;
|
|
109
|
+
if (intervalMs > 0) {
|
|
110
|
+
timer = setInterval(() => {
|
|
111
|
+
try { sweep(); } catch { /* best-effort */ }
|
|
112
|
+
}, intervalMs);
|
|
113
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
sweepNow() {
|
|
117
|
+
try { sweep(); } catch { /* best-effort */ }
|
|
118
|
+
},
|
|
119
|
+
stop() {
|
|
120
|
+
if (timer !== null) {
|
|
121
|
+
clearInterval(timer);
|
|
122
|
+
timer = null;
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Wire the sweeper to a host pi extension lifetime: sweep once on attach
|
|
130
|
+
* (reclaims hosts leaked by a previous host that died without cleanup), run
|
|
131
|
+
* periodically, and sweep again on session_shutdown (host pi exiting or the
|
|
132
|
+
* extension instance being reloaded for a session switch).
|
|
133
|
+
*
|
|
134
|
+
* Child pi processes (AGENT_BOARD_CHILD=1 / AGENT_VIEW_CHILD=1) must never
|
|
135
|
+
* sweep: they share the same board root and would terminate their own runner
|
|
136
|
+
* (suicide chain). They get a strict no-op.
|
|
137
|
+
*
|
|
138
|
+
* @param {{ on?: (event: any, fn: () => void) => any }} pi — any, not a narrower
|
|
139
|
+
* string-keyed signature: pi's ExtensionAPI.on is a union of literal event-name
|
|
140
|
+
* overloads and would otherwise fail structural assignment from TypeScript
|
|
141
|
+
* callers (contravariant parameter check).
|
|
142
|
+
* @param {{ isHostedChild: boolean, sweep: () => void, intervalMs: number }} o
|
|
143
|
+
*/
|
|
144
|
+
export function attachWarmHostSweeper(pi, { isHostedChild, sweep, intervalMs }) {
|
|
145
|
+
// Child pi processes and board-spawned non-host workers must never sweep:
|
|
146
|
+
// children would terminate their own runner (suicide chain); workers
|
|
147
|
+
// (job/state runners set AGENT_BOARD_NO_SWEEP=1) would churn the shared root.
|
|
148
|
+
if (isHostedChild || process.env.AGENT_BOARD_NO_SWEEP === "1") {
|
|
149
|
+
return { active: false, dispose() {} };
|
|
150
|
+
}
|
|
151
|
+
const sweeper = createWarmHostSweeper({ sweep, intervalMs });
|
|
152
|
+
let shutdown = false;
|
|
153
|
+
const onShutdown = () => {
|
|
154
|
+
if (shutdown) return;
|
|
155
|
+
shutdown = true;
|
|
156
|
+
sweeper.sweepNow();
|
|
157
|
+
sweeper.stop();
|
|
158
|
+
};
|
|
159
|
+
pi.on?.("session_shutdown", onShutdown);
|
|
160
|
+
sweeper.start();
|
|
161
|
+
sweeper.sweepNow(); // 回收上一个宿主(可能非正常退出)遗留的 warm hosts
|
|
162
|
+
return {
|
|
163
|
+
active: true,
|
|
164
|
+
dispose() {
|
|
165
|
+
onShutdown();
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git worktree isolation for same-repo parallel writer sessions (plan §11.2).
|
|
3
|
+
* Each managed view gets its own worktree + branch so concurrent writers never clobber.
|
|
4
|
+
*/
|
|
5
|
+
import { execFileSync } from "node:child_process";
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} repoRoot
|
|
10
|
+
* @param {string[]} args
|
|
11
|
+
* @returns {{ ok: boolean, stdout: string, error: string|null }}
|
|
12
|
+
*/
|
|
13
|
+
function git(repoRoot, args) {
|
|
14
|
+
try {
|
|
15
|
+
const stdout = execFileSync("git", ["-C", repoRoot, ...args], {
|
|
16
|
+
encoding: "utf8",
|
|
17
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
18
|
+
windowsHide: true,
|
|
19
|
+
});
|
|
20
|
+
return { ok: true, stdout: stdout.trim(), error: null };
|
|
21
|
+
} catch (err) {
|
|
22
|
+
const e = /** @type {any} */ (err);
|
|
23
|
+
const error = (e?.stderr?.toString?.() || e?.message || "git error").trim();
|
|
24
|
+
return { ok: false, stdout: "", error };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Create a worktree at `worktreePath` on a fresh branch `branch`, based on HEAD.
|
|
30
|
+
* Idempotent-ish: if the path already exists we assume it's already a worktree.
|
|
31
|
+
* @param {string} repoRoot
|
|
32
|
+
* @param {string} worktreePath
|
|
33
|
+
* @param {string} branch
|
|
34
|
+
* @returns {{ ok: boolean, error: string|null }}
|
|
35
|
+
*/
|
|
36
|
+
export function addWorktree(repoRoot, worktreePath, branch) {
|
|
37
|
+
if (existsSync(worktreePath)) return { ok: true, error: null };
|
|
38
|
+
// Try with a new branch first; fall back to attaching if the branch already exists.
|
|
39
|
+
let res = git(repoRoot, ["worktree", "add", "-b", branch, worktreePath, "HEAD"]);
|
|
40
|
+
if (!res.ok && /already exists/i.test(res.error ?? "")) {
|
|
41
|
+
res = git(repoRoot, ["worktree", "add", worktreePath, branch]);
|
|
42
|
+
}
|
|
43
|
+
return { ok: res.ok, error: res.error };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Remove a worktree (forced) and prune. Best-effort.
|
|
48
|
+
* @param {string} repoRoot
|
|
49
|
+
* @param {string} worktreePath
|
|
50
|
+
* @returns {{ ok: boolean, error: string|null }}
|
|
51
|
+
*/
|
|
52
|
+
export function removeWorktree(repoRoot, worktreePath) {
|
|
53
|
+
const res = git(repoRoot, ["worktree", "remove", "--force", worktreePath]);
|
|
54
|
+
git(repoRoot, ["worktree", "prune"]);
|
|
55
|
+
return { ok: res.ok, error: res.error };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A short, filesystem/branch-safe identifier for a view's worktree branch.
|
|
60
|
+
* @param {string} viewId
|
|
61
|
+
* @returns {string}
|
|
62
|
+
*/
|
|
63
|
+
export function worktreeBranch(viewId) {
|
|
64
|
+
return `agent-board/${viewId}`;
|
|
65
|
+
}
|