Repository navigation
feat: nothing starts a turn when a team is created (#645) - #681
Conversation
|
Verdict: PASS at 4af1899 Ran:
Not run:
Reviewed at this tip only. All 9 checks are Core suite
What I checked beyond the tests
Non-blocking
Risk: T3 ( |
28857f0 to
24e7f6d
Compare
|
Verdict: INC at 24e7f6d The removal itself is clean and I can check it: the A message the user typed becomes permanent instructions, and gets no answer. In the sidebar a user types in the composer — the box says "Message Bot" — and, when the text is not an existing Bot's name, Fix, either way keeping the rest of the change: after Ran:
Not run:
Non-blocking:
T3: not enqueued, and no approve is wanted while this verdict stands. |
24e7f6d to
6ddab1c
Compare
Creating a Bot or a group chat sends nothing on its own: no kickoff, no greeting round and no first-request cue. A line the user typed in the composer before creating a Bot is sent to it as one user turn, so the Bot answers it, and it never becomes the Bot's instructions: a brief is instructions only when another Bot creates the Bot. A picked model is applied without a turn, and a Bot's first turn is otherwise a line the user writes to it. Tests cover the three-Bot team, the group, the composer line and a kickoff line in an older transcript. Docs-not-needed: core/dev/smoke.mjs only follows the silent-creation behaviour; docs/subsystems/ds-bot-core.md describes what the script is for, not its bootstrap steps, and the behaviour itself is in core-host.md and core-client.md, both updated here.
6ddab1c to
57d69d4
Compare
|
Verdict: PASS at 57d69d4 The INC at Ran: node --import ./test/harness-resolve.mjs --test --test-timeout=90000 --test-concurrency=6 "test/*.test.js" (in
Ran: node --import ./test/harness-resolve.mjs --test --test-concurrency=3 test/roster.test.js test/rounds.test.js test/new-chat.test.js test/create-bot.test.js test/history.test.js
Ran: GET /repos/ThinkFlowLab/FlyBot/compare/d9f9db07...57d69d4
Ran: GET /repos/ThinkFlowLab/FlyBot/commits/57d69d43/check-runs
Ran (red without the source change): copied the tip, restored all 19 source files (
Not run:
Not blocking, one line each:
|
…is missing (ThinkFlowLab#688) Written-by: Dev The gate already refused a PASS with no `Ran:` line; the rule now lives in AGENTS.md, together with the one way we fix such a PASS without breaking "one conclusion per tip". Tip SHA: 96e4383 ## Reproduce `ThinkFlowLab#681`'s PASS at `4af18994` (05:59:29Z) carries no `Ran:` line, so `merge-gate.mjs` reads it as "lists no command it ran" and refuses the queue entry even with an approval; `ThinkFlowLab#574`'s PASS is the same shape. The refusal is correct — a PASS that ran nothing is not evidence — but the rule it enforces was written only in the script's comments and in `docs/autonomous-dev.md`'s description of it; `AGENTS.md`, which is what the routines and reviewers read first, did not say it. Nothing in this change alters the gate. ## What changed `AGENTS.md`, one bullet in **Gates**: - **A PASS must list what it ran.** The `Ran:` line (or a `Ran …:` heading over `$ ` commands) carrying at least one real command is part of the gate; `merge-gate` refuses a PASS that lists none (ThinkFlowLab#681, ThinkFlowLab#574). - When a PASS at a tip cannot be read for that reason, the reviewer may post **one replacement PASS at the same tip**: the body keeps the original conclusion, adds the `Ran:` lines of what was actually run, and carries `Replaces: the PASS at <sha> merge-gate rejected for having no Ran: line`. That is a replacement, not a second conclusion; apart from it one tip still carries one. No code, no workflow, no policy file, no test changes: the parser and the gate already behave this way. ## What I ran The change is prose, so the check is that the text says exactly what the gate does, and that the existing readers accept the replacement shape: ```text $ node --test .github/scripts/review-gate.test.mjs .github/scripts/merge-gate.test.mjs # tests 69 # pass 69 # fail 0 ``` (That is the suite at the current `ThinkFlowLab#676` tip `ef340354`, not main: main's copies predate the attribution rules, and both versions refuse a PASS with no `Ran:`.) The replacement line itself is an ordinary line to `parseReport` — it is ignored — so a replacement PASS parses as a normal conclusion: verdict on the first line, attribution second, `Ran:` below. ## Red without the source change Not applicable in the usual sense: this adds no behaviour, so there is no test that turns red when the prose is removed. The refusal it documents is already pinned by `merge-gate`'s own case (`a PASS that lists no command it ran is not evidence`), which stays green with and without this bullet. ## Not run - `npm run verify`: docs-only change, no `core/`, plugin or client file touched. - No new test: adding a test that reads AGENTS.md for a sentence would test the copy, not the gate; the gate case already exists. ## Risk T3 by the repository's own `risk-gate` (`AGENTS.md` is a T3 path). The text is the owner's decision of 2026-10-22 (relayed by the Chief): a PASS must carry `Ran:`, and a PASS that `merge-gate` cannot read may be replaced once at the same tip with a `Replaces:` line. It grants no new merge right and changes no gate.
…tay hidden (ThinkFlowLab#698) (ThinkFlowLab#704) Fixes ThinkFlowLab#698 Tip SHA: 7d01614 The two loose ends ThinkFlowLab#681 left: a first send that fails no longer strands a Bot the card cannot reach, and a kickoff line is hidden in the current part of a transcript exactly as it already was in the earlier parts. ## Reproduce 1. **A failed first send.** In the sidebar composer, type "帮我写周报" and pick `New Bot "帮我写周报"`, then Create. `created` (`new-chat.js`) awaited `actions.send` **before** `close()`, so when the send rejected the Bot existed, the card stayed open with its Create button, and a second press answered "a Bot named … already exists" — the user was left holding a Bot they could not reach. Reproduced by driving the post-create step with a rejecting `send`. 2. **A kickoff line in the current part.** On main, `core/src/host/parts.js` (`historyEntry`) skips `source.role === 'kickoff'` and `core/src/client/cells.js` (`TriggerCell`) does not, so a Bot-created Bot's older transcript hid the setup prompt in "earlier parts" and drew it as a `Message from <parent Bot>` row in the current part. Reproduced by rendering that transcript through `TriggerCell`. ## What changed - `core/src/client/bot-draft.js`: new `afterCreate({ id, text, actions })` — open the new Bot's chat and its details, then send the composer's line as its first turn; the send's failure is reported with `actions.notify` instead of escaping into the card. - `core/src/client/new-chat.js`: `created` closes the pane and delegates to `afterCreate`, so the order is open-then-send and the card is never left clickable over an existing Bot. - `core/src/client/cells.js`: `TriggerCell` skips `role === 'kickoff'` beside `role === 'room'`, matching `historyEntry` (the host side needed no change: it already skipped it). - Tests: `core/test/create-bot.test.js` drives `afterCreate` with a rejecting send (chat opened, failure noticed, no "already exists" path) and with an empty composer (nothing sent); `core/test/kickoff-line.test.js` renders a `kickoff` line through `TriggerCell` and runs the same line through `historyEntry`, and checks a real `reply` line still shows in both. `core/test/new-chat.test.js` pins the new `created` wiring. - Docs and changelog: `docs/subsystems/core-client.md` (the ThinkFlowLab#645 section now says where the send and the failure go, and that both paths hide a kickoff line); `changelog.d/698-follow-ups.fixed.md`. ## What I ran At this tip, whose parent is main `2f53801` (`main` moved 5 files while this was pushed; none of them is in this diff): - `core`: `node --import ./test/harness-resolve.mjs --test --test-concurrency=3 "test/*.test.js"` — **1223/1223 pass**. The same command on a pristine copy of the base `cbce2b03` here is **1219 pass, 1 fail** (`check-arch-docs.test.js` "empty diff passes" — that copy has no `origin/main`, an environment failure, not this change). - `npm run lint` in `core`: oxlint **0 errors** (43 pre-existing warnings), `arch-docs: OK (7 changed file(s))` (run with `ARCH_DOCS_BASE` set to the base commit, since this copy has no `origin/main`). - `npm run build` then `node scripts/build-client.mjs --check`: `client.js` matches `src/client`. - `node scripts/ds-bot-patches.mjs generate` then `check`: pin `5834c00` (v0.1.5), 10 patches apply in order, `core/` equals `ds-bot/` + patches. ## Red without the source change - The failure path: with `afterCreate` reverted to the pre-fix shape (send before open, no catch), `core/test/create-bot.test.js` is **6 tests, 5 pass, 1 fail** — `a failed first send still opens the new Bot and shows the failure`. - The transcript: with `cells.js` reverted to `role === 'room'` only, `core/test/kickoff-line.test.js` is **1 test, 0 pass, 1 fail** — `a kickoff line is hidden in the earlier parts and in the current part (ThinkFlowLab#698)`. - Restored: those three touched test files **17/17**, and the full suite 1223/1223. ## Not run - The bench smoke test (`dev/bench.sh smoke`): it needs a DSH checkout and a real model route, which this session has no access to. Nothing here changes `index.js` or `src/host/`. - The CI-only `package` (pack check, typecheck, actionlint) and `merge-gate` jobs: left to this tip's CI. - No browser click of the card: one home only, so `afterCreate` is driven by its unit test and the card's wiring is pinned by `core/test/new-chat.test.js` source assertions, the way that file pins the rest of the new-chat pane. ## Risk - User-visible but small: only the failure path changes shape (you end up on the new Bot's chat with a notice), and a setup prompt that was never a real message stops being drawn. - Scope: `core/src/client/**`, tests, one subsystem doc, one changelog fragment, regenerated `patches/ds-bot/` and the rebuilt `client.js`. No host change, no new config key, no permission model change. - Tier: the repo's own `judge()` over this file list (with `.github/agent/risk-rules.json`) gives **T1** — every source path here is `core/src/client/**` (T1) and the five generated files (client.js, the two patches, the two `files/` test copies) are excluded; the two test files are T1.
…hinkFlowLab#710) Written-by: Dev ## Reproduce At `bafe9bad` the prompt text describes only an active team. The plain-Bot rules never mention staying idle, and the Main Bot rules instruct it to act and report on its own: ``` $ grep -c "report back" core/src/host/prompt.js 2 $ grep -c "stay idle" core/src/host/prompt.js 0 $ grep -n "report back" core/src/host/prompt.js 29: '- You run the user\'s Bot team: you decide who does what, create new Bots, and report back.', 33: '- Route work to the right Bot with message_bot, then tell the user what you asked and that you will report back.', ``` So a team with nothing to do still produces status traffic — the sidebar full of “运行命令…” previews from ThinkFlowLab#645 part B. Part A (silent creation, ThinkFlowLab#681) and part D (burst coalescing, ThinkFlowLab#641/ThinkFlowLab#642) already shipped; this is the half that changes the default from active to idle. ## What changed - `core/src/host/prompt.js`, team rules: one rule states the default — a Bot does nothing until the user addresses it, it is @-mentioned in a group, or its parent Bot gives it a task; it does not start work, send messages or report on its own initiative; being addressed still gets an answer as usual. - `core/src/host/prompt.js`, Main Bot rules: “you decide who does what, create new Bots, and report back” becomes task-driven and states that **a coordinator acts on a user task, not on its own initiative**; “Route work … then tell the user what you asked and that you will report back” loses the unprompted part; one new rule says the same default holds for the Main Bot, which does not hand out work or start a round on its own schedule. - `core/test/prompt.test.js`: a third case asserts both directions — the idle rule appears exactly once in both a Main Bot and a plain Bot prompt, no `report back` and no dispatch instruction survives in either, the coordinator sentence is the Main Bot's, and the reply rule is still pinned (the change is about the unaddressed default, not about replies). - `patches/ds-bot/`: regenerated from `core/`. `0001-core-fixes.patch` carries `src/host/prompt.js` and now `test/prompt.test.js` (upstream already ships that test file, so `topics.json` needed an entry for it — the only other file the regeneration touched). - `docs/subsystems/core-host.md`: the `prompt.js` row records the idle default (this is the file's owned path, so `arch-docs` is satisfied by the same PR). - `docs/subsystems/team.md`: the same two sentences, next to the roles table. - `changelog.d/699-idle-default.changed.md`: one fragment, as the change is user-visible. Seven files, one commit: the two core files, the regenerated patch and `topics.json`, the two docs and the fragment. Nothing else in the tree moves — `git diff` against the base touches exactly those. ## What I ran At tip `43ba4450`: ``` $ node --test core/test/prompt.test.js # tests 3 # pass 3 $ node .github/scripts/changelog.mjs check changelog: 2 fragment(s) in changelog.d/ are well formed $ grep -c "report back" core/src/host/prompt.js 0 $ grep -c "stay idle" core/src/host/prompt.js 1 $ grep -c "dispatch" core/src/host/prompt.js 0 $ node scripts/ds-bot-patches.mjs generate … wrote patches/ds-bot/0001-core-fixes.patch (n files) … $ diff -r patches-main patches/ds-bot # against the committed series at bafe9ba Files …/0001-core-fixes.patch and …/0001-core-fixes.patch differ Files …/topics.json and …/topics.json differ ``` The patch series was then proven by rebuilding: on a copy of the tree, `node scripts/ds-bot-patches.mjs apply` rebuilds `core/` from `ds-bot/` + `files/` + the 10 patches, and `diff -r core ../full699/core` is **empty** — byte for byte. ## Red without the source change `core/src/host/prompt.js` reverted to `bafe9bad` with the new test case kept: `core/test/prompt.test.js` **2 of 3, one red** — the new case fails on its first assertion (“main carries the idle default as its own rule”). The assertion reads the text the prompt module produces, not a copy. ## Not run - The 0-turn behavioural case (ThinkFlowLab#681's “10 Bots + 3 groups, no user message, 0 model turns”) lives in the roster/rounds and new-chat suites, not in `prompt.test.js`. This change edits prompt text only — no turn-starting, delivery or roster code is touched — and CI runs those suites on this tip; I did not run them here. - `node scripts/ds-bot-patches.mjs check` could not finish its first step here: this box has no git checkout of `ds-bot/` (the tree came from an archive, which drops file modes), so the pin's tree hash does not match and the script stops before comparing patches. The comparison it would make is covered above by `apply` + `diff`, and CI runs `check` on a real checkout. - `npm run arch-docs` needs `git` history to fetch `origin/main`; the owned doc for `prompt.js` (`docs/subsystems/core-host.md`) is changed in this PR. - `npm run verify`, the smoke test and the bench: the change is prompt text and CI runs the full suites. ## Risk T2 — `core/src/host/prompt.js`. Text-only for behaviour: no tool, setting, permission or turn-starting path changes, and the reply rules a Bot answers with are unchanged and pinned by the existing case. Tip SHA: 43ba445 Fixes ThinkFlowLab#699
…s judged (ThinkFlowLab#1112) (ThinkFlowLab#1133) Owner: Dev4 Reviewer: Reviewer Base: d797cbe Tip SHA: c27a028 Fixes ThinkFlowLab#1112 ## Reproduce `.github/scripts/review-gate.mjs` decided a tip with `headSha.startsWith(parsed.tip)`, so a conclusion written `Verdict: PASS at abaf938` names every head that begins with those seven characters — including the newest one, which may have no conclusion of its own yet. The guard printed the head short (`short(decision.head)`, 12 characters) and printed no comment id at all, so a reader could not tell which text decided. On ThinkFlowLab#1106 both happened together: green at 05:09:22 on a PASS posted at 05:12:24. ## What changed - `review-gate.mjs`: a conclusion counts only when its tip **is** the head. A shorter tip is refused with its length in the reason ("names abaf938 (7 characters), not the full tip …"). The same comparison in `recordOnlyAt` (`enqueue-rules.mjs`); the deciding entry carries the `id` and `kind` of the comment it read. - `enqueue-guard.mjs`: the report snapshot carries each comment's id; the head is read again after the comments and the diff and a moved head is refused ("the head moved during this run …; run it again"), because otherwise the run judges one head and prints another; the printed line carries the full 40-character head from the value judged, and a second line names the conclusion used. - `gate-check.mjs`: the summary names the comment id and the tip it named. - Docs: `AGENTS.md` ("One conclusion per tip" — the whole 40 characters) and the Verifier skill's tip step, since this tightens what a conclusion must name. - Tests: one per failure mode; the ThinkFlowLab#640 fixture now names its head in full (its point was which line names the tip, not the length). ## What I ran - `TZ=UTC node --test ".github/scripts/*.test.mjs"` → **414 pass, 2 skipped, 0 fail** (416 tests). - `diff -rq` against a `d797cbe4` tree: exactly the 8 files above. ## Red without the source change Against the pristine `d797cbe4` copies of the three sources, the same test file: **4 red**, the four new cases, 59 others green. Each failure mode separately: 1. prefix match restored → **1 red** (the 7-character tip case); 2. comment ids removed from the snapshot → **2 red** (the id case, the printed-summary case); 3. the second head read removed → **1 red** (the moved-head case). Source restored after each knife, `cmp` clean. ## Not run - `npm run verify`: no `core/` change here and no `plugins/` change, so only the `.github/scripts` suite is in scope; `core/` tests never import these files. - `arch-docs`: none of the 8 files is under an `owned_paths` prefix; `AGENTS.md` and `docs/subsystems/` are separate, and the sentence I added is the rule itself, not a subsystem detail. - A live `gh api` run of the guard (needs a token in a job); the fake `gh` in the tests is the same shape the workflow uses. ## Risk - **A short tip now refuses.** Any conclusion written with 7–39 characters stops counting, which is the point, but it fails closed on a report someone wrote short — the reason says the length, and the fix is one more PASS at the same tip (the ThinkFlowLab#681 replacement rule covers the same situation). - **A moved head aborts the run.** Two reads instead of one, and a push between them refuses the run instead of judging a stale pair; the check is re-run on the next event anyway. Cost is one extra API read per run. - The compare is equality on a lower-cased sha; GitHub always returns 40 hex characters for a head, so a legitimate conclusion is unaffected.
Refs #645
Tip SHA: 57d69d4
Docs-not-needed:
core/dev/smoke.mjsonly follows the silent-creation behaviour;docs/subsystems/ds-bot-core.mddescribes what the script is for, not its bootstrap steps.Creating a Bot or a group chat no longer starts work before the user asks. A new Bot is sent no greeting: the
[Bot team setup]kickoff, thekickoffdelivery role, the held greeting and the model-pick greeting are gone. A new group posts no opening round, andcreate-room'sgreetoption is gone. A line the user typed in the composer before creating a Bot is sent to it as one user turn, so the Bot answers it, and it is never saved as the Bot's instructions; abriefbecomes instructions only when another Bot creates the Bot (create_bot). A Bot's first turn is otherwise the user's first line.#645 is five proposals and this is A: B (idle default), C (activity budget) and E (quiet hours) stay open, hence
Refs, notFixes.Reproduce
On
mainbefore this change, making a team speaks to every Bot at once:core/src/host/roster.jsbuilt akickofftext on everycreateBotand delivered it with rolekickoff, so creating three Bots delivered three messages.core/src/host/groups.js(roomKickoff,greetRoom, theopeningSpeakerskickoff branch) posted a "The user just created this group" round oncreate-roomwithgreet: true, and the client always sent that flag.sentis empty after threecreate-botcalls and onecreate-room, and that the first user message then opens exactly one admin turn.new-chat.jspassed that line to the card asbrief, the host folded it intoinstructionsasWhy you exist: 帮我写周报, and the new chat opened empty — a request the user typed became a standing rule and got no answer.What changed
core/src/host/roster.js: no kickoff text, nosendKickoff/dropKickoff, nowaiting/bot.kickoffheld greeting, no greeting inupdateBotorsetMain;createSession'scopyOfparameter is gone. AbriefjoinsinstructionsasWhy you exist: <brief>only whencreatedBynames a Bot: a parent's context, ascreate_botpasses it. Acreate-botrequest with a brief (the new-Bot card, any client) leaves the instructions alone.core/src/client/new-chat.js,create-bot.js,bot-draft.js: the card carries nobriefandcreatePayloadhas nobrieffield.createdsends the composer's line to the new Bot with oneactions.sendbefore it opens the chat, the way opening an existing Bot and creating a group do, andCreateBotawaitsonCreatedso a failed send shows in the form.core/src/host/parts.js:historyEntrystill skips asource.role === 'kickoff'line, so a transcript written before this change does not surface its old greeting prompt as the Bot's own words (and keep the answers after it).core/test/history.test.jskeeps the assertion.core/src/host/groups.js:roomKickoff,greetRoom/rt.greetRoom, the kickoff branch ofroomPrompt,openingSpeakersandisPlainFaceLine, and the round's kickoff note and guard are removed.core/src/host/channel.js:create-roomhas nogreetoption;turns.jshas no kickoff queue branch and nodropKickoffcall;delivery.jsno longer writes the[Bot team setup]header.core/src/host/tools.js:create_botsays the new Bot starts no turn instead of "greeting the user now".new-chat.jsandgroup-settings.jsstop sendinggreet: true, andcells.jsno longer draws a creation turn.core/dev/smoke.mjs: its first two steps assert silent creation (nokickoff, nowaiting, no model call when a model is picked) instead of the greeting this change removes.docs/subsystems/core-host.md,core-client.mdandplugin-runtime.mddescribe silent creation and where a typed line goes; one changelog fragment (changelog.d/645-silent-creation.changed.md).patches/ds-bot/regenerated (four test files got atopics.jsonentry),core/client.jsrebuilt.What I ran
The previous tip (
6ddab1ca) was green in every job exceptarch-docs, which asked for a subsystem doc forcore/dev/smoke.mjs; this tip is the same tree with theDocs-not-needed:trailer above in its commit message.At this tip, whose parent is main
d9f9db07(main changed nine files since the basea41f8606; none of them is in this diff, so nothing of main's is overwritten):core:node --import ./test/harness-resolve.mjs --test --test-concurrency=3 "test/*.test.js"— 1220/1220 pass.npm run lintincore: oxlint 0 errors (43 pre-existing warnings) andarch-docs: OK (44 changed file(s)).npm run buildthennode scripts/build-client.mjs --check:client.jsmatchessrc/client.node scripts/ds-bot-patches.mjs generatethencheck:ds-bot/equals the pin (tree5834c00), 10 patches apply in order,core/equalsds-bot/+ patches.Red without the source change
Three separate reverts, each on this tip's tree, each run with the new tests kept:
roster.jsandgroups.jsto9393d558and ranroster+rounds→ 62 tests, 57 pass, 5 fail, and the five are the new cases (the first Main Bot waits for the user to pick a model, and nothing opens a turn,other Bots start on the default model and are sent nothing,creating a Bot sends it nothing, and only a parent Bot's brief becomes its instructions,a group the user makes starts no turn of its own,creating a team starts no turn, and the user's first message opens one round (#645)).new-chat.js,create-bot.js,bot-draft.jsandroster.jsto the24e7f6dasource (the tip the INC was written against) and rancreate-bot,new-chatandroster→ 38 tests, 35 pass, 3 fail:the payload carries the look and the chosen model, and never a brief (#645),#645: a line typed in the composer is the new Bot's first turn, not its instructions,creating a Bot sends it nothing, and only a parent Bot's brief becomes its instructions.parts.jsand ranhistory→ 7 tests, 6 pass, 1 fail (historyEntry keeps answers, user and Bot messages, and skips setup).The unchanged cases around them (
#410model inheritance,#289, the round and question tests, the handoff and group-chat skips) stay green in all three runs.Not run
dev/bench.sh smoke): it needs a DSH checkout and a real model route, which this session has no access to.core/dev/smoke.mjsis updated to the new behaviour but was not executed.package(pack check, typecheck, actionlint) andmerge-gate: left to this tip's CI.core/test/new-chat.test.js(source assertions on the created flow, the way that file already pins the + menu), not clicked in a browser: one home only here, so the new-Bot card and the sidebar composer were read in the source.Risk
state.jsonsaved by an older version can still carrybot.kickoff(a held greeting). Nothing reads it now, so it is never delivered; it is a few dead bytes per Bot and needs no migration. Say the word and I can strip it on load in a follow-up.core/src/host/**,core/src/client/**, tests,core/dev/smoke.mjs, docs,client.jsand regeneratedpatches/ds-bot/. No change to DSH, no new config key, no permission model change.core/host behaviour at every team creation — T3.