Skip to content

fix(workflow): early-end tip-unchanged/open-PR; arm deadline without ephemeral - #1572

Merged
Birfy merged 1 commit into
ThinkFlowLab:mainfrom
Birfy:birfy/workflow-early-end-deadline
Oct 28, 2026
Merged

Birfy merged 1 commit into
ThinkFlowLab:mainfrom
Birfy:birfy/workflow-early-end-deadline

Conversation

@Birfy

@Birfy Birfy commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Owner: Danyang Chen
Reviewer: Reviewer
Written-by: Grok Bot (box-local; no Cursor CloudAgent)

Fixes #1570
Fixes #1571

Extends #1537 / #1538 / #1539 lineage. LIVE tip 62023599 (2026-10-28 Asia/Shanghai) re-looped Claim↔Verify on an unchanged tip SHA and re-ran Claim when an open PR already existed; loop/onFail max=3 amplified. Morning Claim also sat ~88m — LIVE image predates #1540's deadline, and syncTimers also required run.ephemeral before arming.

What changed

  • checkEarlyEnd: add tip-unchanged and open-pr (OUTPUT / whole-line EARLY-END <reason> / common phrases). Keep needs-human + already-claimed.
  • completeNode / loopDecision: early-end skips success-path loops.
  • failNode + applyOp: early-end wins over onFail recovery (no Claim↔Verify burn).
  • syncTimers: arm STEP_DEADLINE_MS for every live running step, even without an ephemeral map (fix(workflow): STEP_DEADLINE must arm for live steps even without an ephemeral map #1571).
  • EARLY_END_GUIDANCE in template.js + docs for Claim/Verify/Triage prompts (Verify escalates once on unchanged tip).

Verification at the tip

Tip SHA: 46ddc0c85af0eec98e95e0cc7f6c8380da2b8f3d
Ran: cd plugins/workflow && node --import ./test/resolve.mjs --test on all suite files except host.test.js (sandbox missing @deepseek-ai/dsh-llm) → 132 pass / 0 fail (includes 8 new early-end cases + 1 new deadline case). node .github/scripts/changelog.mjs check → well formed.
Not run: host.test.js (env); full npm run verify / prod upgrade (out of scope).

Residual

LIVE Settings graphs may still need operator prompt updates after deploy so Triage/Claim/Verify emit markers. No CloudAgent used; edited on box then PR.

Checkpoint

{
  "pr": 1572,
  "claim": "Danyang Chen",
  "issue": 1570,
  "summary": "checkEarlyEnd now ends runs on tip-unchanged and open-pr (plus needs-human/already-claimed), skips loop and onFail burns on the same tip, arms STEP_DEADLINE without requiring ephemeral, and documents EARLY_END_GUIDANCE for Claim/Verify/Triage.",
  "files": [
    "plugins/workflow/src/graph.js",
    "plugins/workflow/src/service.js",
    "plugins/workflow/src/template.js",
    "plugins/workflow/test/early-end-tip-and-pr.test.js",
    "plugins/workflow/test/deadline.test.js",
    "docs/subsystems/workflow.md",
    "changelog.d/1570-early-end-tip-openpr.fixed.md",
    "changelog.d/1571-step-deadline-no-ephemeral.fixed.md"
  ],
  "checks": {
    "ran": "plugins/workflow node --test (minus host.test.js); changelog check",
    "result": "green",
    "at": "46ddc0c85af0eec98e95e0cc7f6c8380da2b8f3d"
  },
  "next": "none — ready for review; operator may update LIVE Settings prompts after deploy"
}

…ephemeral

Fixes ThinkFlowLab#1570
Fixes ThinkFlowLab#1571

Extends checkEarlyEnd for tip-unchanged and open-pr (plus EARLY-END
directives), skips loop/onFail when those fire, arms STEP_DEADLINE for
live steps without an ephemeral map, and documents Claim/Verify/Triage
prompt guidance in EARLY_END_GUIDANCE.

Owner: Danyang Chen
Reviewer: Reviewer
Written-by: Grok Bot (box-local; no CloudAgent)
@Birfy Birfy added the agent-pr Pull request opened by an agent label Oct 28, 2026
@Birfy

Birfy commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: PASS at 46ddc0c
Written-by: Reviewer
Attestation: 46ddc0c role=Reviewer

Ran:

  • cd plugins/workflow && node --import ./test/resolve.mjs --test $(ls test/*.test.js | grep -v host.test.js) → pass (132 tests, 0 fail; includes early-end tip/open-pr + deadline cases)
  • cd core && npm run arch-docs → pass (8 changed files, workflow.md updated)
  • gh pr checks 1572 → package pass, plugins (workflow) pass

Not run:

  • host.test.js (sandbox missing @deepseek-ai/dsh-llm)
  • Full npm run verify / smoke
  • Prod upgrade (out of scope)

Findings

No actionable findings. Early-end covers tip-unchanged / open-pr / needs-human / already-claimed; loops and onFail do not burn on early-end; STEP_DEADLINE arms without requiring ephemeral. Docs + changelogs present.

Residual

LIVE Settings graphs may still need operator prompt updates after deploy so Triage/Claim/Verify emit EARLY-END markers.

@github-actions github-actions Bot added the pass label Oct 28, 2026
@Birfy
Birfy added this pull request to the merge queue Oct 28, 2026
Merged via the queue into ThinkFlowLab:main with commit 5b125e3 Oct 28, 2026
14 of 15 checks passed
@Birfy
Birfy deleted the birfy/workflow-early-end-deadline branch October 28, 2026 07:59
@github-actions github-actions Bot removed the agent-pr Pull request opened by an agent label Oct 28, 2026
HugoXK pushed a commit to HugoXK/FlyBot that referenced this pull request Oct 28, 2026
dispose() collected the runs of a lane after archiving each live run.
archiveCutoffRun() removes the run from lane.active and makes it lane.last,
so the collection named only the run archived last: with two concurrent runs
the earlier run's run Bots were never released, and write() then erased their
ids, leaving hidden Bots that no state names (ThinkFlowLab#1387). Snapshot the lane before
the archive loop.

Fixes ThinkFlowLab#1472

Rebased onto main 9b866a5 by Dev-4:
kept every change of c740357 (4 files) and resolved the conflicts with
the newer main work on the same rows (ThinkFlowLab#1530, ThinkFlowLab#1572); content unchanged.
HugoXK pushed a commit to HugoXK/FlyBot that referenced this pull request Oct 28, 2026
…ped connector list (ThinkFlowLab#1573)

Fixes ThinkFlowLab#1520

Owner: Chief
Reviewer: Reviewer
Tip SHA: `fc6cddff4eed35dd1b43828965fbcef8f351248b`
Base: `9b866a5c797e8efe95e445885b69da4b8ca5c35b` (main; replayed onto it
after ThinkFlowLab#1514 landed on the same two files)

Docs-not-needed: `plugins/workflow/client.js` copy plus an input-local
draft state; the workflow subsystem doc already states that `node.tools`
names connectors only and every node Bot gets a normal Bot's file and
shell access.

## What changed

**The field explains itself, in English and Chinese.** Under the Tools
input the inspector now says: *Names the connectors this run Bot may
use: github grants mcp__github__\*, composio grants the Composio
connector. File and shell access is always full, like a normal Bot, and
a node that names no connector gets none.* — followed by the same
sentence in Chinese. The placeholder changes from `github, bash, read,
edit...` to `github, composio`, because `bash`, `read` and `edit` are
not connectors and grant nothing here.

**Typing a second connector works.** The value used to be rebuilt from
the saved list on every keystroke (`(selected.tools ?? []).join(', ')`)
while `onChange` saved immediately; the host's `cleanTools` drops a
trailing empty token, so the re-render ate the separator and `github,
composio` typed one character at a time saved as the single token
`githubcomposio`. `stepGrants` knows only `github`, `composio` and
`mcp__<id>__*`, and `restrict()` had already taken the connector tools
away, so the node ran with no connector and nothing said so. The input
now keeps a local draft (`toolsDraft`) and commits it on blur or after a
600 ms pause; typing and pasting end in the same saved list. The host
normalization and the spawn path are untouched.

## Verification at the tip

Tip SHA: `fc6cddff4eed35dd1b43828965fbcef8f351248b`

Ran:
- `npm --prefix plugins/workflow test` at the tip → **138 / 138 / 0**.
- Red first, same test file on main's `client.js` blob: **43 / 45 / 2**
— `the Tools field says it names connectors and that file and shell
access is always full` failed on `the Tools field explains what it
names`; `the Tools field takes a second connector typed one character at
a time` failed with actual `'github'` against expected `'github,'` after
the ninth character.
- `npm --prefix plugins/social test` 527 / 527 / 0, `plugins/tree` 125 /
125 / 0, `plugins/timeline` 125 / 125 / 0.
- `node core/scripts/build-client.mjs --check` clean, `node
scripts/ds-bot-patches.mjs check` clean, `node scripts/lint-plugins.mjs`
0 errors, `node .github/scripts/changelog.mjs check` 60 fragments well
formed.
- Core suite sequentially: **1866 / 1868 / 2**; `core/` is
byte-identical to main in this branch and both failures are this sandbox
— `empty diff passes` needs `git` (absent here), `fixture usage.sqlite
has one real calls row with all five fields` reads a fixture this
extraction does not carry.

Not run (and why):
- `npm run verify` end to end: this sandbox has no `git` binary, so `npm
--prefix core run lint` stops at `arch-docs: FAIL — cannot diff against
<base>` (the oxlint half ran: 0 errors, 63 pre-existing warnings). The
`Docs-not-needed:` trailer is in the branch commit and in this body for
the CI half that can diff.
- No smoke run and no browser screenshot: `core/src/host/`, the plugin's
host seams and `src/ephemeral.js` are untouched.

## Risk

T2: the diff is under `plugins/**`, so it needs a PASS at this tip plus
green `tests-ok` before the queue. The behavior change is confined to
the inspector input: a typed list now commits on blur or after 600 ms
instead of on every keystroke, so a save can land up to 600 ms later
than before but never later than leaving the field, and the unmount
cleanup drops a pending timer. The saved shape, the host normalization
(`cleanTools`), `stepGrants` and the spawn path are unchanged, so no run
sees a different `node.tools`. `main` moved twice while this was open
(ThinkFlowLab#1572, then ThinkFlowLab#1514 on these same two files); the branch was replayed
each time and the diff is still only these three paths.

## Checkpoint
<!-- flybot:checkpoint -->
```json
{
 "pr": 1573,
 "claim": "Chief",
 "issue": 1520,
 "summary": "The inspector's Tools field now says it names the connectors the run Bot may use (github → mcp__github__*, composio → Composio), that file and shell access is always a normal Bot's, and that naming none reaches none, in English and Chinese. Its placeholder is github, composio, not bash, read and edit. The field keeps a local draft and commits on blur or a pause, so a second connector typed by hand no longer loses its separator: before, the host's trailing-token normalization saved githubcomposio and silently refused the connector. Only client.js, its test and one changelog fragment change.",
 "files": [
  "plugins/workflow/client.js",
  "plugins/workflow/test/client.test.js",
  "changelog.d/1520.fixed.md"
 ],
 "checks": {
  "ran": "npm --prefix plugins/workflow test (138/138); npm --prefix plugins/social|tree|timeline test; node core/scripts/build-client.mjs --check; node scripts/ds-bot-patches.mjs check; node scripts/lint-plugins.mjs",
  "result": "green",
  "at": "fc6cddff4eed35dd1b43828965fbcef8f351248b"
 },
 "next": "none — ready for review; T2 needs a PASS at this tip and green tests-ok before the queue."
}
```
<!-- /flybot:checkpoint -->
HugoXK added a commit to HugoXK/FlyBot that referenced this pull request Oct 28, 2026
…igest skips the health module's count compensation (ThinkFlowLab#1579)

Fixes ThinkFlowLab#1578

Owner: Dev-1
Reviewer: Reviewer
Written-by: Dev-1
Risk: T2 — `plugins/timeline/src/digest.js` is plugin code
(`.github/agent/risk-rules.json`), so this needs a PASS at the tip plus
green CI. No T3 path, no dependency change.
Tip SHA: `8139276924c95ec4f30691244c8e4c3d2030ebe0`
Tree: `bd8db4c70d8271142d4798b42b8405d73ff9953f`
Base: `c1b9fd23e404dcff1eb782a2269e30e09e2d6993` — live `main` (`ThinkFlowLab#1547`,
`ThinkFlowLab#1572`, `ThinkFlowLab#1556`, `ThinkFlowLab#1552`, `ThinkFlowLab#1573`).

## What was wrong

The report compared two different populations.
`plugins/timeline/src/window.js:249` counts `turns` as the turns that
**began** in the window; `:250` counts `errors` as every **overlapping**
turn whose outcome is `error`. A failed turn that began before the
window is therefore in `errors` and not in `turns`, and `digest.js`
divided one by the other: `const errorRate = turns > 0 ? errors / turns
: 0`, printed in three places.

That is exactly the reading `docs/subsystems/timeline.md` invariant 5
says can never happen — the error rate "divides by the larger of the
turns that began in the window and the failed turns, so it never reads a
failure count above its turn count" — and
`plugins/timeline/src/health.js:27-32` already compensates with
`Math.max(use.turns, use.errors)` and a comment naming the failure text
it avoids. The digest did not.

Reproduced at `main`, real `buildWindow` + `buildDigest`, 15-minute
window, one Bot `b1`: three turns that begin 3/4/5 minutes before the
window and fail 20/21/22 s into it, plus one turn that begins inside it
and completes. The window holds 4 turns (`outcomes {completed: 1, error:
3}`, `utilization.turns` 1, `errors` 3), and the report read:

- per Bot: `- 1 turns, 3 failed (300%)`
- Team: `- 1 turns, 3 failed`
- one line: `1 turns, 3 failed, 0 tokens in over 07:45–08:00.`

The mild shape is worse than a bad percentage: one overlapping failed
turn plus one completed turn that began inside the window reads `1
turns, 1 failed (100%)`, i.e. "the window's only turn failed", when that
turn is the completed one. `1 turns` was also printed everywhere.

## What changed

`plugins/timeline/src/digest.js` only — three lines of arithmetic and
the three printers:

- `counted = Math.max(turns, errors)`, and `errorRate = errors /
counted`, the same denominator `health.js` uses. `counted` is now on
each `bots[]` entry and on `team`, so a consumer can read the number the
share was taken over; `turns` and `errors` keep their raw window values.
- The per-Bot line, the `## Team` line and the one-line summary all
print `counted`, so the number compared with the failure count is the
number printed, and the Team rollup is the sum of the Bot sections, as
before.
- The failure flag reads `${errors} of ${counted} turns failed`, the
wording `health.js` already uses, and `no turns` now needs `counted ===
0` rather than `turns === 0` (a Bot whose failed turns all began before
the window is not "no turns" and is not reported as one).
- Singular/plural follows the number in all three places (`1 turn`, `3
turns`).
- A share is printed only when it can be a share, i.e. below 100%. With
every counted turn a failed one, the numbers cannot say whether that
turn began in the window or before it; `(100%)` beside a window whose
single completed turn began inside it reads as its opposite. At that
point the report prints the counts and no percentage (`3 turns, 3
failed`), which is still consistent and never over 100%.

Scope and what I did not touch:

- `window.js`'s two counters are unchanged — they are documented and the
panel reads them.
- `health.js` is unchanged; the digest now matches it instead of
diverging.
- `plugins/timeline/client.js:588` (the panel's Utilization line, same
pair of numbers) is **not** changed. It prints two raw counts and no
rate, so it cannot overclaim a share; changing the count it prints would
make the panel disagree with the documented meaning of
`utilization.turns` (invariant 5) that the rest of the panel and
`health.js` rely on. If the owner wants the panel to print the
comparison denominator too, it is a separate, one-line change with its
own test.
- `changelog.d/1578-digest-error-rate.fixed.md` added. Docs unchanged:
invariant 5 already states the rule this change implements.

`Docs-not-needed: docs/subsystems/timeline.md invariant 5 (line 52)
already states this rule — the error rate divides by the larger of the
turns that began in the window and the failed turns, so it never reads
above its turn count. This change makes digest.js obey that sentence; it
adds and changes no documented behaviour, and window.js, health.js and
client.js are untouched.`

Same line in the branch commit (`git log -1 --format=%B`), so the
trailer survives the squash into the commit `main` judges, and it is
what turns `arch-docs` green at the tip: `arch-docs: FAIL` was the only
reason the first tip's `tests-ok` was red.

## Red → green

At the base with only `test/digest.test.js` added (source at `main`):
**13 tests, 10 pass, 3 fail** —

- `ThinkFlowLab#1578: three failed turns that began before the window never read as
300% of one turn`
- `ThinkFlowLab#1578: a failed turn that began before the window is not read as the
window's own turn failing`
- `ThinkFlowLab#1578: a single turn reads as one turn in all three places`

The fourth new test, `ThinkFlowLab#1578: a clean window is byte-identical to the
report before the change`, passes on both sides by design: it is the
pinned output (`turns: 3, errors: 0`) that proves the denominator does
not move when `errors <= turns`.

At the tip: `test/digest.test.js` **13/13**, `plugins/timeline`
**129/129** (base 125 + these 4).

## Verification at the tip

Tip `8139276924c95ec4f30691244c8e4c3d2030ebe0`, tree
`bd8db4c70d8271142d4798b42b8405d73ff9953f`, base
`c1b9fd23e404dcff1eb782a2269e30e09e2d6993`, 3 files `+86/−10`
(`digest.js` 20/10, `test/digest.test.js` 65/0, changelog 1/0).

Ran:
- `npm --prefix plugins/timeline test` → 129/129, 0 fail.
- `TZ=UTC ARCH_DOCS_BASE=c1b9fd23e4 npm run verify` → exit 0: core 1869,
social 527, tree 125, timeline 129, workflow 142, `.github/scripts` 548,
interop 37, live-e2e 24 — 3401 tests, 0 fail; oxlint 0 errors (core 63 /
social 36 / timeline 9 / tree 2 / workflow 13 warnings); `arch-docs: OK
(3 changed file(s))` — the two plugin files are covered by the
`Docs-not-needed:` trailer below; `build-client --check` and
`ds-bot-patches check` clean.
- `node .github/scripts/changelog.mjs check` → 63 fragments well formed.
- `cd core && ARCH_DOCS_BASE=c1b9fd23e4 node
scripts/check-arch-docs.mjs` → `arch-docs: OK (3 changed file(s))` at
the tip with the `Docs-not-needed:` trailer in both places (first tip:
`FAIL` on the same two files, `tests-ok` red for that reason alone).

Not run: the smoke test (no `core/index.js` or `src/host/` change), the
`package` job, Desktop, and the owner's live instance.

## Checkpoint
<!-- flybot:checkpoint -->
```json
{
  "pr": 1579,
  "claim": "Dev-1",
  "issue": 1578,
  "summary": "The team report divided overlapping failed turns by the turns that began in the window, so a failure from before the window printed as 1 turns, 3 failed (300%). The digest now counts against max(turns, errors), the denominator health.js already uses, in the per-Bot line, the Team line and the one-line summary, with the team total the sum of the Bot sections. It prints a share only below 100%, since with every counted turn a failed one the numbers cannot say whose turn failed, and singular/plural follows the count. window.js, health.js and client.js are untouched.",
  "files": ["plugins/timeline/src/digest.js", "plugins/timeline/test/digest.test.js", "changelog.d/1578-digest-error-rate.fixed.md"],
  "checks": { "ran": "TZ=UTC ARCH_DOCS_BASE=c1b9fd23 npm run verify", "result": "green", "at": "8139276924c95ec4f30691244c8e4c3d2030ebe0" },
  "next": "none — ready for review at the tip"
}
```
<!-- /flybot:checkpoint -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

1 participant