Conversation
added 7 commits
September 21, 2026 20:31
Task package: sih-engine/sih/state/plan/run-lifecycle.md (PR1, first half). - Tombstone soft delete on terminal runs (succeeded/failed/ completed_with_gaps/cancelled/interrupted): run body gains deletedAt/deletedBy/purgeAfter plus an append-only run.deleted audit event; steps, events, result and the integrity ledger stay untouched. - Query faces all filter tombstones: run list, snapshot (workflow_status, HTTP GET), cross-run reuse candidate scan, repair source scan, resume, and requestId idempotent replay (rejected with a recovery hint instead of resurrecting ghost data). - Restore clears the tombstone and appends run.restored; the run reappears byte-identical. Repeat deletes are idempotent and never append a second event. - Retention: trashRetentionDays setting (default 30, 0 = manual-only expiry) via the store settings face; changing it restamps tombstones already in trash so the displayed countdown stays truthful. - Exposure: HTTP DELETE /api/runs/:id, POST /api/runs/:id/restore, GET /api/runs?trash=1, GET/POST /api/trash; MCP workflow_delete / workflow_restore (old daemons gated via the trashManagement feature flag); Studio trash view with restore button and retention countdown. - checks/trash.check.mjs pins hide/restore round trips, every filtered face, idempotence, the reuse interaction, retention math and the HTTP/MCP surfaces; tool-count assertions move from 11 to 13.
Task package: sih-engine/sih/state/plan/run-lifecycle.md (PR1, second half).
- Rotation compaction exports due tombstones (purgeAfter <= now) into the
sidecar <data-dir>/archive.db and physically deletes their live runs/steps
rows; the events table never loses a row (program red line). The audit
event archive.rotated {rotationId, runs, manifestHash, runCount, bytes} is
appended in the same transaction as the deletes, anchoring the manifest on
the MiniMax-AI#48 events hash chain.
- manifestHash is computed by one shared implementation
(archiveManifestHash: canonical JSON over runs+steps rows in (runId, id)
order) used by both rotation and verification, so the two hashes cannot
drift; checks/archive-rotate.check.mjs recomputes it independently.
- Archive rows are keyed by (rotationId, runId) instead of a bare runs PK:
a run that is restored, re-deleted and re-rotated writes a fresh copy and
can never rewrite rows an earlier rotation's manifest still covers.
- Verification (verifyArchive) recomputes every rotation's manifest from
the archived rows and compares it against both the rotations record and
the chained event; chained-but-missing rotations and orphan archive rows
fail closed. verifyIntegrity() gains an archive face alongside
events/repair without touching their semantics.
- Restore from archive copies rows back (idempotent upserts), clears the
tombstone and appends run.restored {origin:'archive', rotationId}; a
requestId claimed by a newer live run blocks the restore loudly.
workflow_delete on a rotated run reports the archive state truthfully.
- Triggers: CLI --rotate-archive (with --verify for the shared verify
outlet; null verdicts from empty chains are not failures) and --restore
<runId>, both forwarding to a live service instead of fighting the owner
lock; service startup auto-rotates when tombstones exceed 500 or the
runs table exceeds ~100MB (constants documented in store.mjs), skipped
entirely when trashRetentionDays=0 (manual-only). HTTP face:
POST /api/archive/rotate.
- checks/archive-rotate.check.mjs pins due-only rotation, the events-row
red line, tamper detection (step body, forged rotations hash, deleted
rotation record), archive restore and re-rotation verifiability, the
501-vs-400 startup threshold against a real daemon, and the CLI faces
with real exit codes.
…eage Task package: sih-engine/sih/state/plan/run-lifecycle.md, PR2 (复跑谱系), stacked on PR1's tombstone/restore/rotation commits. A rerun starts a NEW pending_review run from the source's script+input and never writes the source. The child carries rerunOf/lineageRoot/rerunSeq on its body, so the family survives unlimited reruns, trash and archive rotation (members are annotated, never removed). Key decisions: - requestId is synthesized as `<root>#rerun-<n>`; n counts live, tombstoned AND archived family members so a seq is never reused after rotation, with a numeric-suffix retry when the id is already claimed (150-char cap kept). - reuseAcrossRuns defaults to false on reruns (explicit opt-in only): silent adoption would turn a rerun into a fake execution and poison comparison. - Reruns pass through the existing pending_review/approve gate; the rerun action itself only creates the draft, it never auto-executes. Tombstoned sources are refused until restored; rerunning straight from the archive is deliberately not offered (restore first). - GET /api/runs/:id/lineage resolves the family from any member id (live, tombstoned or archived), ordered root-first then by seq, with per-member status/duration/timestamps/result summary; ?results=1 adds full results for the read-only compare face. Durations come from the events ledger, which rotation never moves. - MCP workflow_rerun (input override + reuseAcrossRuns flag) is gated behind a rerunLineage feature flag, mirroring the trash tools' old-daemon guard. - Studio gains a lineage panel (member list with trash/archive flags) and a two-column read-only JSON result compare. - New checks/rerun-lineage.check.mjs pins: source byte-identity across a rerun, family ordering over 3 reruns, default no-reuse vs explicit reuse, trash/rotation refusal with family integrity, requestId collision retry, HTTP lineage shape and the MCP schema. Files: src/store.mjs src/engine.mjs src/http.mjs src/tools.mjs src/main.mjs web/index.html web/app.source.mjs web/app.js web/i18n.mjs web/style.css checks/rerun-lineage.check.mjs checks/package.check.mjs checks/workspace-router.check.mjs test/package.test.mjs README.md dist/main.mjs
Fork preview run 35506090983 (first full windows-latest pass) failed check MiniMax-AI#9 "packaged MCP advertises reuseAcrossRuns and accepts it through the public tool surface" with: EBUSY: resource busy or locked, rmdir 'C:\Users\RUNNER~1\AppData\Local\Temp\wf-cross-mcp-NCVTMk' Every assertion passed; the failure came from the finally block. StdioClientTransport.close() resolving does not mean the spawned dist/main.mjs server process has exited, so the child still held the dataDir (the mkdtemp tmp dir) handle when rm() ran, and win32 refuses to rmdir a busy directory. Linux/darwin unlink open files, which is why all local runs were green. - add a local rmWithRetry helper: retry rm on EBUSY/ENOTEMPTY/EPERM with exponential backoff (200ms base, x2, 5 retries, ~6s total budget), then rethrow as-is -- cleanup failures stay loud, we only give the OS time to release handles - finally: wrap client/transport close in try/catch so a close error cannot skip rm; rethrow the close error after cleanup so it still fails the test Only checks/cross-reuse-mcp.check.mjs is touched; no src/, no dist/, no other checks, no new dependencies. Local verification: npm test exit=0, 126/126.
…-reuse MCP check Round two of the same failure. Fork preview run 35506404071 (with 14a7fd5) failed the same check MiniMax-AI#9 with the same code: EBUSY: resource busy or locked, rmdir '...\wf-cross-mcp-3VJDll' The test ran 7945ms -- the full ~6.2s retry budget burned while the directory stayed locked. Not a transient lock, so retrying cannot fix it. Real root cause (src/main.mjs, stdio branch): when no existing service is found, --stdio spawns a detached daemon -- spawn(process.execPath, args, {cwd:workspace, detached:true, ...}); child.unref() -- with cwd set to the workspace (the mkdtemp tmp dir), service.log and the database inside dataDir (the same tmp dir). By design "the service, workers and dashboard outlive" the chat transport, so client.close()/transport.close() only tear down the stdio face and the daemon keeps running, holding its cwd handle plus service.log/DB handles inside the tree. win32 refuses to rmdir a directory tree a live process is sitting in; Linux/darwin unlink open files, which is why local runs stay green. Layer two, from run 35506090983: transport.close() resolving does not mean a child has fully exited, so handle release can lag an instant even after the daemon is gone. The codebase's own convention already stops the service first (checks/fail-loud.check.mjs:80, checks/lifecycle.check.mjs:19): run dist/main.mjs --stop-service --workspace <dir> --data-dir <dir> after closing the client. Identity matches this test because its --settings file sets workspace=dataDir=dir, the same endpoint.json the daemon registered under. - finally: after client/transport close (still deferred-rethrow), run --stop-service wrapped in try/catch so a stop failure cannot block rm - keep rmWithRetry as the release-lag fallback, still failing loud when exhausted - rewrite the comment to the two-layer root cause with both run ids Only checks/cross-reuse-mcp.check.mjs is touched; no src/, no dist/, no new dependencies. Local verification: npm test exit=0, 126/126; ps confirms the daemon spawned by the check is actually terminated by the stop-service step (no stray wf-cross-mcp-* processes remain).
…r flags Task package: sih-engine/sih/state/plan/run-lifecycle.md, PR2 (复跑谱系), UI patch from the egolite real-panel interaction pass. Two bugs, one render path, fixed as one decision: data refreshes, interaction state survives. Bug 1 (compare view wiped by polling): with two members selected, clicking 对比结果 shows the two-column diff (measured 1090x138 visible); ~6s later the poll re-render hides it again (hidden:true, 0x0). Root cause: renderLineage() ran unconditionally on every poll (refreshCurrent -> renderRun -> renderLineage) and executed `#lineage-diff.hidden=true` plus a full select rebuild each pass, resetting the user's diff visibility and left/right selection every few seconds. Bug 2 (stale deleted flag after trash restore): restoring rerun-1 left its lineage card wearing the 已删除(回收站) flag across 8s+ and multiple poll cycles even though the lineage API already returned deleted:null (three members live, trash empty). Root cause: the flag renders from the client's lineage cache, which was only refetched on selectRun and panel toggle - neither happens on restore, so polls kept painting stale data forever (not a DOM-residue issue: rows were rebuilt from stale input). Fix: - renderLineage() now splits data from interaction. Member cards (status, duration, trash/archive flags, preview) re-render on every pass from whatever the cache holds. The compare controls (selects, open diff) are rebuilt only when member identity changes, gated by a pure signature (language + ordered id:rerunSeq:name, web/lineage-model.mjs). Signature unchanged -> selects untouched, #lineage-diff.hidden untouched. Member added/removed/reordered/renamed or language switch -> full rebuild and the diff resets (member set changed, reset is legitimate). First render is unchanged (empty previous signature always rebuilds). - refreshCurrent() refetches the lineage payload (background mode) when the panel is open, so member state can no longer go stale across polls; background errors keep the last good cache instead of blanking the open panel. Trash restore additionally triggers an immediate refetch. - Regression nail: checks/lineage-panel.check.mjs pins the gate logic in node:test (poll-shaped refresh and flag clears never rebuild; identity/ language/name changes always do). The DOM-level outcome (diff stays visible across 6s+ of polling) cannot be carried by node:test (no DOM); to be re-verified with the egolite interaction protocol: click compare -> still visible after 6s+ of polling. Files: plugins/hetaoBackend/mcode-dynamic-workflows/web/app.source.mjs plugins/hetaoBackend/mcode-dynamic-workflows/web/lineage-model.mjs plugins/hetaoBackend/mcode-dynamic-workflows/web/app.js (rebuilt via scripts/build-web.mjs) plugins/hetaoBackend/mcode-dynamic-workflows/checks/lineage-panel.check.mjs
User request: "工作流画布应该也可以变大,在结构拓扑前添加按钮,点击后 可以全屏" - the run-detail canvas (structure topology block) gets a button that expands the canvas to the full screen; clicking again or pressing Esc exits. What lands: - Button placement: #graph-fullscreen sits first in the canvas toolbar action row (web/index.html), immediately before the 结构拓扑 toggle (#graph-mode), so it precedes the topology block as requested. Label is bilingual via i18n keys canvasFullscreen / canvasExitFullscreen (web/i18n.mjs); aria-pressed tracks the mode and renderRun re-syncs the label so a language switch while fullscreen keeps the exit semantics. - Two fullscreen forms, API first with overlay fallback: the click asks the panel's requestFullscreen() (webkit prefix covered) and only falls back to an opaque fixed overlay (.canvas-overlay, position:fixed inset:0, z-index 40 - web/style.css) when the API is unavailable or denied (embedded iframe without allow=fullscreen, permission refusal; the rejected promise and webkitfullscreenerror both route to the fallback). Esc exits either form: natively for the API form, via a document keydown handler for the overlay. The toolbar stays inside the fullscreen panel, so the exit control is always reachable. - Interaction state survives: switching runs or polling re-renders graph content without leaving fullscreen (data refreshes, fullscreen state stays on the panel), Esc never exits past an open dialog (node reader / report keep their own close), and service disconnects cannot strand the canvas - the exit paths are document-level and unconditional. When the run view empties (no run), fullscreen is released as state hygiene. - After each geometry change (enter/exit either form) zoomAuto is reset and renderGraph() refits once to the new panel size; pan/zoom interaction is the existing canvas machinery, untouched. - Pure logic extracted: web/fullscreen-model.mjs holds the mode transitions (requestMode / rejectApi / apiChange / shouldExitOnKey). Regression nail checks/canvas-fullscreen.check.mjs pins the fallback order, the dialog precedence for Escape, and the button position ahead of #graph-mode. Files: web/index.html, web/i18n.mjs, web/app.source.mjs, web/fullscreen-model.mjs, web/style.css, checks/canvas-fullscreen.check.mjs, web/app.js (via scripts/build-web.mjs), dist/main.mjs (i18n keys propagate through src/reports.mjs -> bundled translate). Verified: npm test 148 pass, npm run build byte-stable across two runs. DOM-level behaviour (button render, both fullscreen forms, refit, poll persistence) is to be confirmed through the egolite interaction protocol on the real panel.
Contributor
Author
|
Closing for now — folding in additional changes before resubmitting. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat(studio): fullscreen button for the workflow canvas
A "Fullscreen" button is added to the canvas toolbar, placed before the topology-mode toggle in the structure-topology section. Clicking it expands the canvas to fill the viewport; clicking again (or pressing Esc) returns it to its inline size.
requestFullscreen, webkit prefix included); when it is unavailable or rejected (embedded pages, iframe restrictions, permission denial) the canvas automatically falls back to a fixed-position opaque overlay (position:fixed; inset:0; 100dvh) — same behavior either way, with the toolbar (zoom, topology mode, exit button) staying inside the panel.web/fullscreen-model.mjs) and pinned bychecks/canvas-fullscreen.check.mjs(6 cases: fallback order, idempotent re-entry, rejection downgrade, fullscreenchange tracking, dialog-first Esc, button placement). DOM-level outcomes (enter → survives polls → button flips to "Exit fullscreen" → Esc exits and the panel returns to inline size) were verified interactively against the real dashboard.Suites:
npm test142 → 148 green; Linux clean-tree gate green; Windows + CodeQL via the fork preview mirror. Bilingual i18n (zh/en); no new dependencies; stacked on #59's branch and rebases cleanly once it merges.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.