diff --git a/changelog.d/2843-project-issue-ref.fixed.md b/changelog.d/2843-project-issue-ref.fixed.md new file mode 100644 index 000000000..4d5273261 --- /dev/null +++ b/changelog.d/2843-project-issue-ref.fixed.md @@ -0,0 +1 @@ +The source comments, plugin README and subsystem pages that describe the Project domain as its own plugin now cite [#2830](https://github.com/ThinkFlowLab/FlyBot/issues/2830) instead of an unrelated workflow-store fix; no behavior changed. See [#2843](https://github.com/ThinkFlowLab/FlyBot/issues/2843). diff --git a/changelog.d/project-plugin.added.md b/changelog.d/project-plugin.added.md index f60c2e523..0c3db133f 100644 --- a/changelog.d/project-plugin.added.md +++ b/changelog.d/project-plugin.added.md @@ -1 +1 @@ -The Project domain now ships as its own peer plugin, `@flybot/project` (`plugins/project`), instead of living inside the core: Project State, the Project board, the Project's own Chief, the team dossier, the Project metrics and the Project Assistant moved out of `core/src/host/project-*.js`, and the `project_state` / `project_digest` tools are registered by that plugin. Settings → Plugins gains a **Project** switch that turns the whole domain off at the next start; with it off (or with the plugin not installed) the core keeps its documented fallbacks — a Project is created without a Chief or an assistant and the board and the dossier answer empty. Nothing a Project does changes: same reads, same refusals, same strings, and every existing test moved with the code. See [#2684](https://github.com/ThinkFlowLab/FlyBot/issues/2684). +The Project domain now ships as its own peer plugin, `@flybot/project` (`plugins/project`), instead of living inside the core: Project State, the Project board, the Project's own Chief, the team dossier, the Project metrics and the Project Assistant moved out of `core/src/host/project-*.js`, and the `project_state` / `project_digest` tools are registered by that plugin. Settings → Plugins gains a **Project** switch that turns the whole domain off at the next start; with it off (or with the plugin not installed) the core keeps its documented fallbacks — a Project is created without a Chief or an assistant and the board and the dossier answer empty. Nothing a Project does changes: same reads, same refusals, same strings, and every existing test moved with the code. See [#2830](https://github.com/ThinkFlowLab/FlyBot/issues/2830). diff --git a/core/src/host/constants.js b/core/src/host/constants.js index 47007441c..0c7c635fc 100644 --- a/core/src/host/constants.js +++ b/core/src/host/constants.js @@ -27,7 +27,7 @@ export const MAX_HOPS = 6 // character note was refused as "over 600 characters". Count with `[...text].length`, as // `chat-index.js` and `busy-ack.js` already do, and keep both writers on that same unit. export const MAX_ENTRY = 600 -// The role label a Project Assistant carries (#2684). The meetings module refuses a close from +// The role label a Project Assistant carries (#2830). The meetings module refuses a close from // this role, and the Project assistant (now in plugins/project) creates the Bot with it; the seam // hands the plugin this same constant, so the two cannot spell it differently. export const ASSISTANT_ROLE = 'Project Assistant' diff --git a/core/src/host/day-key.js b/core/src/host/day-key.js index 45ce6cdd8..0525ee4b7 100644 --- a/core/src/host/day-key.js +++ b/core/src/host/day-key.js @@ -1,4 +1,4 @@ -// The natural day a timestamp falls in, in the caller's time zone (#2684). `en-CA` prints ISO +// The natural day a timestamp falls in, in the caller's time zone (#2830). `en-CA` prints ISO // order, which is what the comparison needs; an unknown zone throws here, so a caller that takes // one from outside (a board read) resolves it with `resolveTimeZone` first. // diff --git a/core/src/host/hooks.js b/core/src/host/hooks.js index 6ddd70338..a7b875f80 100644 --- a/core/src/host/hooks.js +++ b/core/src/host/hooks.js @@ -1161,7 +1161,7 @@ export function install(rt) { // apply(), and registers nothing when this answers false. Read from the state file // the last run wrote, because plugins load before the team is loaded. pluginEnabled: id => pluginEnabledAtStart(rt.statePath, id), - // The Project domain's host (#2684): plugins/project implements Project State, the board, the + // The Project domain's host (#2830): plugins/project implements Project State, the board, the // Chief, the dossier, the metrics and the assistant, and cannot import core/src/*. It asks for // the runtime and the shared pure helpers once, in its own apply(); a core without the plugin // never calls this and keeps the store's existing fallbacks. diff --git a/core/src/host/plugins.js b/core/src/host/plugins.js index 5421e9aa6..06f3bb227 100644 --- a/core/src/host/plugins.js +++ b/core/src/host/plugins.js @@ -1,5 +1,5 @@ // The FlyBot feature plugins and the owner's switch for each (#2564). The Project domain is one -// of them since #2684: its code lives in plugins/project and this switch is how an owner turns +// of them since #2830: its code lives in plugins/project and this switch is how an owner turns // the whole domain (tools, board, Chief, assistant) off without uninstalling anything. // // The switch does not install or uninstall anything: the packages stay in the DSH diff --git a/core/src/host/project-host.js b/core/src/host/project-host.js index 90cf84fae..368893630 100644 --- a/core/src/host/project-host.js +++ b/core/src/host/project-host.js @@ -1,4 +1,4 @@ -// The Project domain's host seam (#2684). The domain — Project State, the board, the Project's own +// The Project domain's host seam (#2830). The domain — Project State, the board, the Project's own // Chief, the team dossier, the metrics and the assistant — moved to `plugins/project`, which ships // as its own package and therefore cannot import `core/src/*`. The core hands it the two things it // cannot get by itself: the runtime it puts its seams on, and the pure helpers the two packages diff --git a/core/src/host/work-dir.js b/core/src/host/work-dir.js index 655c3299d..6476e3d27 100644 --- a/core/src/host/work-dir.js +++ b/core/src/host/work-dir.js @@ -1,4 +1,4 @@ -// The Session's working folder (#2684): the cwd its Agent runs in, else the folder the Bot or room +// The Session's working folder (#2830): the cwd its Agent runs in, else the folder the Bot or room // is set to work in, else the team workspace. Undefined only when none of them is known. // // This lived in `project-state.js` while that reader was the only caller; the meetings module diff --git a/core/test/agents.test.js b/core/test/agents.test.js index 1f1a48a61..2a76329de 100644 --- a/core/test/agents.test.js +++ b/core/test/agents.test.js @@ -91,7 +91,7 @@ function start(team, { config = {} } = {}) { return register(options) } apply(ctx, { home, ...config }) - // The Project domain is a peer plugin (#2684): a profile mounts it next to the core, and the + // The Project domain is a peer plugin (#2830): a profile mounts it next to the core, and the // team tools below include the two it registers. applyProject({ flybot: ctx.flybot }) const call = async (endpoint, payload) => { diff --git a/core/test/install.test.js b/core/test/install.test.js index 97fd6b66e..53acddead 100644 --- a/core/test/install.test.js +++ b/core/test/install.test.js @@ -30,7 +30,7 @@ function start(state, { config = {}, services } = {}) { // Listeners run, and tools are listed, in the order they are registered. // The Project tools (`project_state`, `project_digest`) are registered by @flybot/project's own -// apply() since #2684, so they are not in the core's list; plugins/project/test/host.test.js holds +// apply() since #2830, so they are not in the core's list; plugins/project/test/host.test.js holds // that half. test('apply registers listeners, tools, and effects in a fixed order', (t) => { const { ctx, stop, applyResult } = start() diff --git a/core/test/project-domain.js b/core/test/project-domain.js index 11ea44109..ec2221444 100644 --- a/core/test/project-domain.js +++ b/core/test/project-domain.js @@ -1,4 +1,4 @@ -// The Project domain, applied to a runtime the way a profile has it (#2684). +// The Project domain, applied to a runtime the way a profile has it (#2830). // // The domain — Project State, the board, the Project's own Chief, the team dossier, the metrics // and the assistant — lives in `plugins/project` now, and it installs its seams in its own diff --git a/core/test/run-bot-tools.test.js b/core/test/run-bot-tools.test.js index f5d0c2f31..2cf592d37 100644 --- a/core/test/run-bot-tools.test.js +++ b/core/test/run-bot-tools.test.js @@ -56,7 +56,7 @@ async function start(config = {}) { sessions: { flush: async () => {} }, }) apply(ctx, { home, ...config }) - // The Project domain is a peer plugin (#2684): a profile mounts it next to the core, and the + // The Project domain is a peer plugin (#2830): a profile mounts it next to the core, and the // team tools below include the two it registers. applyProject({ flybot: ctx.flybot }) // A fresh Session per run Bot, the way workflows.js gets one. diff --git a/core/types/flybot.d.ts b/core/types/flybot.d.ts index bfea43ee4..22ecbe28f 100644 --- a/core/types/flybot.d.ts +++ b/core/types/flybot.d.ts @@ -380,7 +380,7 @@ export interface ModelAsk { } export type ModelAnswer = { ok: true; text: string } | { ok: false; code: string } -/** What `flybot.projectHost()` answers: the core runtime and the shared pure helpers (#2684). */ +/** What `flybot.projectHost()` answers: the core runtime and the shared pure helpers (#2830). */ export interface ProjectHost { runtime: Record helpers: Record @@ -397,7 +397,7 @@ export interface FlyBot { /** True while another dsh holds the team: no polling, no writes. */ readOnly(): boolean /** - * The Project domain host seam (#2684), for `plugins/project` only: the runtime the domain + * The Project domain host seam (#2830), for `plugins/project` only: the runtime the domain * installs its seams on (`rt.projectBoard`, `rt.projectChief`, `rt.projectTeam`, * `rt.projectAssistant`) and the pure helpers the two packages share. `undefined` on a host * without it. Not a stable surface for third-party plugins, and absent is not an error: the diff --git a/docs/subsystems/core-host.md b/docs/subsystems/core-host.md index 371c8ec29..21c3731c5 100644 --- a/docs/subsystems/core-host.md +++ b/docs/subsystems/core-host.md @@ -53,9 +53,9 @@ The server side of the patched `ds-bot` core (`core/`): the part of [ds-bot-core | `core/src/host/usage.js` | Usage ledger at `$DSH_HOME/bot/usage.sqlite` (`calls`). Ledger v3 (#903): every row carries `harness`, `issue_pr`, `tool_calls`, `wall_ms`, plus existing token columns (`withUsageFields` / `missingUsageFields`). Session turns are metered via `foldCalls` over turn events (`tool/call` count, wall time from attempt start, `issuePr` from turn text or `rt.noteUsageIssuePr(sessionId, ref)`), not from llm/stream alone. A triage tier is stamped with `rt.noteUsageHarness(sessionId, idOrLabels)` and lands on the row's `harness` field; the resume cursor (`marks.fold`) stays provider / model / retry. Sample row fixture: `core/test/fixtures/usage-sample.sqlite`. Tests: `core/test/usage-fields.test.js`, `core/test/usage.test.js`. | | `core/src/host/tool-schema.js` | The tool definitions the model sees: its `defineTool` wraps DSH's and completes the compiled schema with `explicitObjectShape`, which fills `properties: {}` and `required: []` on every object node (recursing through `properties`, `items`, `oneOf`/`anyOf`/`allOf`, `$defs`, …). A strict OpenAI-compatible gateway reads an absent member as null and rejects the whole request (`null is not of type "array"`), so one argument-less tool would break every turn; argument validation keeps using DSH's schema unchanged. Every host module registers tools through this wrapper. Tests: `core/test/tool-schema.test.js`. | | `core/src/host/real-dir.js` | The one "what is this directory entry, here" check (#2055), shared by the readers that must not follow a folder link. `dirKind(path)` (async) and `dirKindSync(path)` (sync) answer `real` — the entry itself is a directory and not a symlink; `absent` — nothing at that path (`ENOENT`); or `other` — a symlink, a plain file, a non-directory parent, or any other read error (fail closed). Three states rather than a boolean because a writer has to tell "nothing there, create it" from "a link or a file, refuse"; a reader only tests `=== 'real'`. Both shells share one predicate (`lstat` + `isDirectory()` + `!isSymbolicLink()`), so the rule is written once; `#2056` and `#2057` consume it instead of a fourth hand-rolled copy. The leaf door is the same question asked of a file a writer appends to or truncates ([#2079](https://github.com/ThinkFlowLab/FlyBot/issues/2079)): `fileKind(path)` / `fileKindSync(path)` answer `real` (a plain file carrying one name), `absent` (nothing at that path) or `other` (a link, a hard link whose bytes are another name's, a directory, a non-directory parent, or a check that could not run). Since [#2159](https://github.com/ThinkFlowLab/FlyBot/issues/2159) the leaf door and the temporary name a write lands through share one predicate, so the two answers for the same file cannot disagree (`nlink === 1` in both); the five callers today are all writers — the readers ask `dirKind` — so a hard link refuses the write and never turns a snapshot's file into an unreadable one. Tests: `core/test/real-dir.test.js`. | -| `core/src/host/project-host.js` | The Project domain's host seam (#2684): `projectHost(rt)` answers `{ runtime, helpers }`, which is what `plugins/project` asks for in its own `apply()`. The domain — Project State, the board, the Project's own Chief, the team dossier, the metrics and the assistant — moved out of `core/src/host/project-*.js` into [project](project.md); the core must not import a peer plugin, so the plugin takes the runtime it installs its seams on (`rt.projectBoard`, `rt.projectChief`, `rt.projectTeam`, `rt.projectAssistant`) and the pure helpers the two packages share. `HELPER_NAMES` is the list `test/host.test.js` holds the plugin's own list against, and a core with no plugin installed never calls it: the store's Project endpoints then keep the fallbacks they already have (a Project is created without a Chief or an assistant, the board and the dossier answer empty). | -| `core/src/host/work-dir.js` | The Session's working folder (#2684): the cwd its Agent runs in, else the folder the Bot or room is set to work in, else the team workspace. It lived in `project-state.js` while that reader was its only caller; `meetings.js` needs the same answer (the folder a decision is archived in) and the Project domain moved to `plugins/project`, so it is its own module and the plugin reads it back through the host seam. | -| `core/src/host/day-key.js` | The natural day a timestamp falls in, in the caller's time zone (#2684): `en-CA` prints ISO order, `Intl.DateTimeFormat` refuses an unknown zone (a caller that takes one from outside resolves it first), and a non-finite timestamp answers `''`. It was exported by `project-metrics.js`; the core's turn budget needs the same key and the metrics moved to `plugins/project`, so it lives here and the plugin reads it back through the host seam. | +| `core/src/host/project-host.js` | The Project domain's host seam (#2830): `projectHost(rt)` answers `{ runtime, helpers }`, which is what `plugins/project` asks for in its own `apply()`. The domain — Project State, the board, the Project's own Chief, the team dossier, the metrics and the assistant — moved out of `core/src/host/project-*.js` into [project](project.md); the core must not import a peer plugin, so the plugin takes the runtime it installs its seams on (`rt.projectBoard`, `rt.projectChief`, `rt.projectTeam`, `rt.projectAssistant`) and the pure helpers the two packages share. `HELPER_NAMES` is the list `test/host.test.js` holds the plugin's own list against, and a core with no plugin installed never calls it: the store's Project endpoints then keep the fallbacks they already have (a Project is created without a Chief or an assistant, the board and the dossier answer empty). | +| `core/src/host/work-dir.js` | The Session's working folder (#2830): the cwd its Agent runs in, else the folder the Bot or room is set to work in, else the team workspace. It lived in `project-state.js` while that reader was its only caller; `meetings.js` needs the same answer (the folder a decision is archived in) and the Project domain moved to `plugins/project`, so it is its own module and the plugin reads it back through the host seam. | +| `core/src/host/day-key.js` | The natural day a timestamp falls in, in the caller's time zone (#2830): `en-CA` prints ISO order, `Intl.DateTimeFormat` refuses an unknown zone (a caller that takes one from outside resolves it first), and a non-finite timestamp answers `''`. It was exported by `project-metrics.js`; the core's turn budget needs the same key and the metrics moved to `plugins/project`, so it lives here and the plugin reads it back through the host seam. | | `core/src/shared/artifact.js` | The typed facts a workflow node takes in and hands out (#1272): `parseArtifactSpec` reads `check:tests-ok` / `issue.ready` / `text:topic` into `{ kind, name }` and refuses a spelling the vocabulary does not have; `artifact(spec, revision, { value })` is one instance, and `REVISION_RULES` says what the revision of each kind is (a verdict its SHA, a check the SHA it ran on, a PR its number and `headRefOid`). `freshness(input, currentRevision)` answers present-and-current separately from present, and a fact nobody could read is `unreadable`, never a match. Tests: `core/test/workflow.test.js`. | | `core/src/shared/preview.js` | What a stored line may show a person (#130, #1347): `visibleText(text)` unwraps a complete `` wrap, then removes leftover marker fragments until neither changes the text, then folds to one line. `stripFence` is the marker half alone. The sidebar rows read it (`previewOf` in `src/client/text.js`), and so does the `preview` this channel puts on a room in the `state` payload, which used to be a plain `unwrapUntrusted` and so leaked `from="User"> … >` on a line whose opener had already been cut. Tests: `core/test/ui-clickthrough.test.js`. | | `core/src/shared/failure-copy.js` | A failed turn's sentence a person can act on (#1347): `failureSentence(raw, translate)` keys on the provider's own `code` field in the stored text — not on the message, which is not stable — and returns the mapped sentence (naming the feature in an `UnsupportedModel` refusal, and where to change the model), or the text unchanged when the code is not mapped, because a failure nobody has mapped reads better as it came than as a guess. `src/shared/turn-failures.js` and `src/client/inbox.js` both read it, so the chat's dead-turn lines and the Inbox card say the same thing. Tests: `core/test/ui-clickthrough.test.js`. | diff --git a/docs/subsystems/plugin-runtime.md b/docs/subsystems/plugin-runtime.md index 85055efdb..220a4ac5e 100644 --- a/docs/subsystems/plugin-runtime.md +++ b/docs/subsystems/plugin-runtime.md @@ -25,7 +25,7 @@ FlyBot's plugin API surface on top of the patched core: host hooks, client slots - `core/types/flybot.d.ts` — public types. - Flybot unit tests (`flybot.test.js`, `flybot-client.test.js`, `flybot-feed.test.js`); `flybot-client.test.js` also pins the one name a mention wears on every surface — menu insert, bubble chip, sidebar preview, at-source row — and that two friends' same-named Bots stay apart while every alias still routes to its own handle (#967), the sidebar footer button (Connect apps opens Connectors), the account menu's Plugins row, the account menu's labels (Connect apps, Scheduled tasks opening Settings → Scheduled tasks, Settings, and the only classic Agent mode switch; #392-#394), and a face-room row that keeps its full title on hover (#304) while the number-first title wraps (#1102). -- The Project domain's host seam (#2684): `flybot.projectHost()` answers `{ runtime, helpers }` for `plugins/project`, whose code ships as its own package and therefore cannot import `core/src/*`. The core never imports that package; a core without it installed never calls the seam and the store keeps its Project fallbacks. The helpers it hands over are the pure functions the two packages share (`cut`, `codePoints`, `dirKind`, `defineTool`, `output`, `columnOf`, `priorityOf`, `advanceRefusal`, `workDirFor`, `dayKey`, `MAX_ENTRY`, `ASSISTANT_ROLE`, the quiet-hours reads), listed in `core/src/host/project-host.js` and read once in the plugin's own `apply()`. The concrete domain is [project](project.md). +- The Project domain's host seam (#2830): `flybot.projectHost()` answers `{ runtime, helpers }` for `plugins/project`, whose code ships as its own package and therefore cannot import `core/src/*`. The core never imports that package; a core without it installed never calls the seam and the store keeps its Project fallbacks. The helpers it hands over are the pure functions the two packages share (`cut`, `codePoints`, `dirKind`, `defineTool`, `output`, `columnOf`, `priorityOf`, `advanceRefusal`, `workDirFor`, `dayKey`, `MAX_ENTRY`, `ASSISTANT_ROLE`, the quiet-hours reads), listed in `core/src/host/project-host.js` and read once in the plugin's own `apply()`. The concrete domain is [project](project.md). ## Does not own diff --git a/docs/subsystems/project.md b/docs/subsystems/project.md index 70953dcd4..d22e037cf 100644 --- a/docs/subsystems/project.md +++ b/docs/subsystems/project.md @@ -51,7 +51,7 @@ package owns everything that reads or acts on that record. | `plugins/project/src/project-board.js` | The board read model (#1696): `state.tasks`, `state.meetings`, the `decisions/*.md` files and the Project bindings, with the workflow run refs read through the core's provider seam (`rt.onProjectProvider`). Read-only by construction; it writes nothing. Carries the role policy (`rolesOf`, `roleOf`, `roleChoices`, `changeRole` — one Owner, nobody promotes themselves, a Project always keeps its Owner), the queue/lease/blocked groups and the decision card (`decisionCardOf`, `advanceRefusal`). | | `plugins/project/src/project-chief.js` | The Project Chief (#1859): one hidden Bot per Project, named `Project Chief ()`, bound with `bindProject` and given instructions that name that one Project and forbid answering for any other; retired through the host's own `deleteBot` with the Project. `rt.projectChief` is `{ create, chiefOf, remove }`. | | `plugins/project/src/project-team.js` | The team dossier (#1864), read-only: `rt.projectTeam(projectId)` answers `{ rows }` in the board's order — the Chief, the assistant, then `roles.maintainers` / `roles.members` — with persona (the Bot's instructions), the head and topic names of its own memory, its skills and only this Project's rooms. Every string and list is capped and a deleted Bot is a `null` row, never an error for the whole answer. | -| `plugins/project/src/project-metrics.js` | The board's seven numbers (#1829) read from GitHub REST and the local task store, with a five-minute cache and one honest failure answer: a missing credential or an unreadable source is `unavailable` and every value reads `null`, never `0`. The lists are followed page by page under a cap (#2650), a foreign host is never followed with the token, and the day boundary is the caller's zone (`resolveTimeZone`) rather than the host's UTC clock. Its day key is `core/src/host/day-key.js` since #2684. | +| `plugins/project/src/project-metrics.js` | The board's seven numbers (#1829) read from GitHub REST and the local task store, with a five-minute cache and one honest failure answer: a missing credential or an unreadable source is `unavailable` and every value reads `null`, never `0`. The lists are followed page by page under a cap (#2650), a foreign host is never followed with the token, and the day boundary is the caller's zone (`resolveTimeZone`) rather than the host's UTC clock. Its day key is `core/src/host/day-key.js` since #2830. | | `plugins/project/src/project-assistant.js` | The Project Assistant (#1712): one hidden Bot and Session per Project with exactly one 09:00 Asia/Shanghai `project_digest` task on that Session through the host's schedule path (never a cron in the plugin), per-owner memory in `state.projectMemory` behind the store's write door (600 code points), and a digest built only from board rows and Project State (what merged, what closed, what is in progress and on whom, the next slice; a day with no change is one line, a second build answers `skipped`). Its writes carry `Written-by: Project Assistant ()` / `Posted-for: `. | | `plugins/project/client.js` | Empty in this slice: the browser half is still in the core, see **Does not own**. | diff --git a/patches/ds-bot/0001-core-fixes.patch b/patches/ds-bot/0001-core-fixes.patch index 8a5cc9e13..b1134e0ed 100644 --- a/patches/ds-bot/0001-core-fixes.patch +++ b/patches/ds-bot/0001-core-fixes.patch @@ -9628,7 +9628,7 @@ diff --git a/test/agents.test.js b/test/agents.test.js return register(options) } apply(ctx, { home, ...config }) -+ // The Project domain is a peer plugin (#2684): a profile mounts it next to the core, and the ++ // The Project domain is a peer plugin (#2830): a profile mounts it next to the core, and the + // team tools below include the two it registers. + applyProject({ flybot: ctx.flybot }) const call = async (endpoint, payload) => { @@ -12929,7 +12929,7 @@ diff --git a/test/install.test.js b/test/install.test.js // Listeners run, and tools are listed, in the order they are registered. +// The Project tools (`project_state`, `project_digest`) are registered by @flybot/project's own -+// apply() since #2684, so they are not in the core's list; plugins/project/test/host.test.js holds ++// apply() since #2830, so they are not in the core's list; plugins/project/test/host.test.js holds +// that half. test('apply registers listeners, tools, and effects in a fixed order', (t) => { const { ctx, stop, applyResult } = start() diff --git a/patches/ds-bot/0002-flybot-api-host.patch b/patches/ds-bot/0002-flybot-api-host.patch index 24e2b2aec..a4cb3db52 100644 --- a/patches/ds-bot/0002-flybot-api-host.patch +++ b/patches/ds-bot/0002-flybot-api-host.patch @@ -100,7 +100,7 @@ diff --git a/src/host/constants.js b/src/host/constants.js +// character note was refused as "over 600 characters". Count with `[...text].length`, as +// `chat-index.js` and `busy-ack.js` already do, and keep both writers on that same unit. +export const MAX_ENTRY = 600 -+// The role label a Project Assistant carries (#2684). The meetings module refuses a close from ++// The role label a Project Assistant carries (#2830). The meetings module refuses a close from +// this role, and the Project assistant (now in plugins/project) creates the Bot with it; the seam +// hands the plugin this same constant, so the two cannot spell it differently. +export const ASSISTANT_ROLE = 'Project Assistant' diff --git a/patches/ds-bot/files/src/host/day-key.js b/patches/ds-bot/files/src/host/day-key.js index 45ce6cdd8..0525ee4b7 100644 --- a/patches/ds-bot/files/src/host/day-key.js +++ b/patches/ds-bot/files/src/host/day-key.js @@ -1,4 +1,4 @@ -// The natural day a timestamp falls in, in the caller's time zone (#2684). `en-CA` prints ISO +// The natural day a timestamp falls in, in the caller's time zone (#2830). `en-CA` prints ISO // order, which is what the comparison needs; an unknown zone throws here, so a caller that takes // one from outside (a board read) resolves it with `resolveTimeZone` first. // diff --git a/patches/ds-bot/files/src/host/hooks.js b/patches/ds-bot/files/src/host/hooks.js index 6ddd70338..a7b875f80 100644 --- a/patches/ds-bot/files/src/host/hooks.js +++ b/patches/ds-bot/files/src/host/hooks.js @@ -1161,7 +1161,7 @@ export function install(rt) { // apply(), and registers nothing when this answers false. Read from the state file // the last run wrote, because plugins load before the team is loaded. pluginEnabled: id => pluginEnabledAtStart(rt.statePath, id), - // The Project domain's host (#2684): plugins/project implements Project State, the board, the + // The Project domain's host (#2830): plugins/project implements Project State, the board, the // Chief, the dossier, the metrics and the assistant, and cannot import core/src/*. It asks for // the runtime and the shared pure helpers once, in its own apply(); a core without the plugin // never calls this and keeps the store's existing fallbacks. diff --git a/patches/ds-bot/files/src/host/plugins.js b/patches/ds-bot/files/src/host/plugins.js index 5421e9aa6..06f3bb227 100644 --- a/patches/ds-bot/files/src/host/plugins.js +++ b/patches/ds-bot/files/src/host/plugins.js @@ -1,5 +1,5 @@ // The FlyBot feature plugins and the owner's switch for each (#2564). The Project domain is one -// of them since #2684: its code lives in plugins/project and this switch is how an owner turns +// of them since #2830: its code lives in plugins/project and this switch is how an owner turns // the whole domain (tools, board, Chief, assistant) off without uninstalling anything. // // The switch does not install or uninstall anything: the packages stay in the DSH diff --git a/patches/ds-bot/files/src/host/project-host.js b/patches/ds-bot/files/src/host/project-host.js index 90cf84fae..368893630 100644 --- a/patches/ds-bot/files/src/host/project-host.js +++ b/patches/ds-bot/files/src/host/project-host.js @@ -1,4 +1,4 @@ -// The Project domain's host seam (#2684). The domain — Project State, the board, the Project's own +// The Project domain's host seam (#2830). The domain — Project State, the board, the Project's own // Chief, the team dossier, the metrics and the assistant — moved to `plugins/project`, which ships // as its own package and therefore cannot import `core/src/*`. The core hands it the two things it // cannot get by itself: the runtime it puts its seams on, and the pure helpers the two packages diff --git a/patches/ds-bot/files/src/host/work-dir.js b/patches/ds-bot/files/src/host/work-dir.js index 655c3299d..6476e3d27 100644 --- a/patches/ds-bot/files/src/host/work-dir.js +++ b/patches/ds-bot/files/src/host/work-dir.js @@ -1,4 +1,4 @@ -// The Session's working folder (#2684): the cwd its Agent runs in, else the folder the Bot or room +// The Session's working folder (#2830): the cwd its Agent runs in, else the folder the Bot or room // is set to work in, else the team workspace. Undefined only when none of them is known. // // This lived in `project-state.js` while that reader was the only caller; the meetings module diff --git a/patches/ds-bot/files/test/project-domain.js b/patches/ds-bot/files/test/project-domain.js index 11ea44109..ec2221444 100644 --- a/patches/ds-bot/files/test/project-domain.js +++ b/patches/ds-bot/files/test/project-domain.js @@ -1,4 +1,4 @@ -// The Project domain, applied to a runtime the way a profile has it (#2684). +// The Project domain, applied to a runtime the way a profile has it (#2830). // // The domain — Project State, the board, the Project's own Chief, the team dossier, the metrics // and the assistant — lives in `plugins/project` now, and it installs its seams in its own diff --git a/patches/ds-bot/files/test/run-bot-tools.test.js b/patches/ds-bot/files/test/run-bot-tools.test.js index f5d0c2f31..2cf592d37 100644 --- a/patches/ds-bot/files/test/run-bot-tools.test.js +++ b/patches/ds-bot/files/test/run-bot-tools.test.js @@ -56,7 +56,7 @@ async function start(config = {}) { sessions: { flush: async () => {} }, }) apply(ctx, { home, ...config }) - // The Project domain is a peer plugin (#2684): a profile mounts it next to the core, and the + // The Project domain is a peer plugin (#2830): a profile mounts it next to the core, and the // team tools below include the two it registers. applyProject({ flybot: ctx.flybot }) // A fresh Session per run Bot, the way workflows.js gets one. diff --git a/patches/ds-bot/files/types/flybot.d.ts b/patches/ds-bot/files/types/flybot.d.ts index bfea43ee4..22ecbe28f 100644 --- a/patches/ds-bot/files/types/flybot.d.ts +++ b/patches/ds-bot/files/types/flybot.d.ts @@ -380,7 +380,7 @@ export interface ModelAsk { } export type ModelAnswer = { ok: true; text: string } | { ok: false; code: string } -/** What `flybot.projectHost()` answers: the core runtime and the shared pure helpers (#2684). */ +/** What `flybot.projectHost()` answers: the core runtime and the shared pure helpers (#2830). */ export interface ProjectHost { runtime: Record helpers: Record @@ -397,7 +397,7 @@ export interface FlyBot { /** True while another dsh holds the team: no polling, no writes. */ readOnly(): boolean /** - * The Project domain host seam (#2684), for `plugins/project` only: the runtime the domain + * The Project domain host seam (#2830), for `plugins/project` only: the runtime the domain * installs its seams on (`rt.projectBoard`, `rt.projectChief`, `rt.projectTeam`, * `rt.projectAssistant`) and the pure helpers the two packages share. `undefined` on a host * without it. Not a stable surface for third-party plugins, and absent is not an error: the diff --git a/plugins/project/README.md b/plugins/project/README.md index 521d2eceb..7a67ed644 100644 --- a/plugins/project/README.md +++ b/plugins/project/README.md @@ -5,7 +5,7 @@ The Project domain of FlyBot as a peer plugin (built on ds-bot / DeepSeek Harnes ## English ### What it does -- **The Project domain, out of the core (#2684).** Project State, the Project board, the Project's +- **The Project domain, out of the core (#2830).** Project State, the Project board, the Project's own Chief, the team dossier, the Project metrics and the Project Assistant used to be `core/src/host/project-*.js`. They are this package now: `apply()` installs them on the core runtime through `ctx.flybot.projectHost()`, so `rt.projectBoard`, `rt.projectChief`, @@ -70,7 +70,7 @@ integration cases that read a Project through the store, the channel and the cli ## 中文 ### 做什么 -- **Project 域整体搬出 core(#2684)**:Project State、项目看板、项目自己的 Chief、团队档案、 +- **Project 域整体搬出 core(#2830)**:Project State、项目看板、项目自己的 Chief、团队档案、 项目指标和项目助理原本是 `core/src/host/project-*.js`,现在是这个包。`apply()` 通过 `ctx.flybot.projectHost()` 把它们装回 core 运行时,所以 `rt.projectBoard`、`rt.projectChief`、 `rt.projectTeam`、`rt.projectAssistant` 仍是 core 的 store、meetings 与 channel 调用的那几个接缝。 diff --git a/plugins/project/client.js b/plugins/project/client.js index daed96ccf..4fcf11f5b 100644 --- a/plugins/project/client.js +++ b/plugins/project/client.js @@ -1,7 +1,7 @@ // Browser half of @flybot/project, in the dsh client loader format ds-bot uses // (window.__ModuleLoader__.load, CJS factory, `require` resolves shell-provided react). // -// Empty on purpose in this slice (#2684): the Project pages the core draws — the board overlay, the +// Empty on purpose in this slice (#2830): the Project pages the core draws — the board overlay, the // Project row in the header, the projects nav, the settings panels — are still `core/src/client/ // project-board.js` and its callers, because they read the core's own client context (the roster // feed, the overlay stack, the api client) and are bundled by `core/scripts/build-client.mjs`. diff --git a/plugins/project/index.js b/plugins/project/index.js index 336dc8fd7..80888e4fb 100644 --- a/plugins/project/index.js +++ b/plugins/project/index.js @@ -1,7 +1,7 @@ // @flybot/project host plugin (dsh / cordis plugin shape, same as ds-bot: name + inject + apply). // The Project domain — Project State, the board, the Project's own Chief, its team dossier, its // metrics and its assistant — lives in src/, moved out of the core's `src/host/project-*.js` -// (#2684). The core owns the runtime and the few pure helpers the two packages share; this +// (#2830). The core owns the runtime and the few pure helpers the two packages share; this // plugin asks for them through `flybot.projectHost()` and installs the same seams on that // runtime the core used to install itself, so `rt.projectBoard`, `rt.projectChief`, // `rt.projectTeam`, `rt.projectAssistant` and the `project_state` / `project_digest` tools are @@ -22,7 +22,7 @@ export function apply(ctx) { // store keeps its own fallback (a Project is created without a Chief or an assistant), which is // the behavior that core has when its domain modules are not installed at all. if (seam === undefined || seam.runtime === undefined) { - ctx.flybot?.warn?.('flybot-project: this core has no Project host seam, so none of the Project domain is registered (#2684)') + ctx.flybot?.warn?.('flybot-project: this core has no Project host seam, so none of the Project domain is registered (#2830)') return { project: undefined, dispose() {} } } bindHost(seam.helpers) diff --git a/plugins/project/src/host.js b/plugins/project/src/host.js index 44b066a3d..1ac0efb2c 100644 --- a/plugins/project/src/host.js +++ b/plugins/project/src/host.js @@ -1,4 +1,4 @@ -// The helpers this package takes from its host (#2684). The Project plugin ships as its own +// The helpers this package takes from its host (#2830). The Project plugin ships as its own // package, so its code cannot import `core/src/*`; the core hands the few pure helpers the two // packages share over through `flybot.projectHost()` in the plugin's `apply()`. // diff --git a/plugins/project/src/install.js b/plugins/project/src/install.js index 0f7e7cbba..7e467097f 100644 --- a/plugins/project/src/install.js +++ b/plugins/project/src/install.js @@ -1,4 +1,4 @@ -// The Project domain on the host runtime (#2684). The core used to install these six modules +// The Project domain on the host runtime (#2830). The core used to install these six modules // from `core/src/host/project-*.js`; it now hands the plugin its runtime through // `flybot.projectHost()` and this file puts the same seams on it in the same order, so the core's // store, meetings and channel call `rt.projectBoard`, `rt.projectChief`, `rt.projectTeam` and diff --git a/plugins/project/src/project-metrics.js b/plugins/project/src/project-metrics.js index 973a3cbdf..0b27fd9dc 100644 --- a/plugins/project/src/project-metrics.js +++ b/plugins/project/src/project-metrics.js @@ -51,7 +51,7 @@ export function resolveTimeZone(timeZone = 'UTC') { } } -// The day boundary this module compares in moved to `core/src/host/day-key.js` (#2684): the +// The day boundary this module compares in moved to `core/src/host/day-key.js` (#2830): the // core's turn budget needs the same key, and the plugin takes it back through the host seam. // Re-exported so a test of the board's numbers reads it from the same module. export { dayKey } diff --git a/plugins/project/src/project-state.js b/plugins/project/src/project-state.js index 22db2a587..ab963d378 100644 --- a/plugins/project/src/project-state.js +++ b/plugins/project/src/project-state.js @@ -159,7 +159,7 @@ export async function readListedFile(dir, path) { return { ok: true, path, bytes: bytes.length, sha256: sha256(bytes), mtime: new Date(info.mtimeMs).toISOString(), text: bytes.toString('utf8') } } -// The Session's working folder moved to `core/src/host/work-dir.js` (#2684): the meetings module +// The Session's working folder moved to `core/src/host/work-dir.js` (#2830): the meetings module // needs it too, and the plugin takes it back through the host seam. Re-exported so a caller of // this reader (`project_state`, the board) keeps importing it from the same module. export { workDirFor } diff --git a/plugins/project/test/host.test.js b/plugins/project/test/host.test.js index 8c40aae49..eb5fa1865 100644 --- a/plugins/project/test/host.test.js +++ b/plugins/project/test/host.test.js @@ -1,4 +1,4 @@ -// The plugin's own half (#2684): what `apply()` puts on a core runtime, and what the owner switch +// The plugin's own half (#2830): what `apply()` puts on a core runtime, and what the owner switch // takes away. The domain's behavior is covered by the tests next to this file, which moved here // from `core/test/project-*.test.js`; this one pins the plugin boundary itself: // @@ -55,7 +55,7 @@ const tools = ctx => [...ctx.registeredTools.keys()].sort() // Project domain adds and the seams it installs. const projectTools = ctx => tools(ctx).filter(name => name.startsWith('project_')) -test('#2684 apply() installs the Project domain on the core runtime and registers its tools', () => { +test('#2830 apply() installs the Project domain on the core runtime and registers its tools', () => { const { ctx, rt, result, stop } = boot() try { assert.deepEqual(projectTools(ctx), ['project_digest', 'project_state']) @@ -72,7 +72,7 @@ test('#2684 apply() installs the Project domain on the core runtime and register } finally { stop() } }) -test('#2684 the owner switch off registers no tool and no seam', () => { +test('#2830 the owner switch off registers no tool and no seam', () => { const { ctx, rt, result, stop } = boot({ projectEnabled: false }) try { assert.deepEqual(projectTools(ctx), []) @@ -86,7 +86,7 @@ test('#2684 the owner switch off registers no tool and no seam', () => { } finally { stop() } }) -test('#2684 project_state answers the Project State listing it always did', async () => { +test('#2830 project_state answers the Project State listing it always did', async () => { const dir = mkdtempSync(join(tmpdir(), 'flybot-project-state-')) mkdirSync(join(dir, '.git')) writeFileSync(join(dir, 'PROJECT.md'), '# Project\n') @@ -101,7 +101,7 @@ test('#2684 project_state answers the Project State listing it always did', asyn } finally { stop(); rmSync(dir, { recursive: true, force: true, maxRetries: 5 }) } }) -test('#2684 the plugin binds exactly the helpers the core seam names', () => { +test('#2830 the plugin binds exactly the helpers the core seam names', () => { // `test/host-bind.js` bound the core's own implementations for the pure-function tests; the names // the plugin insists on and the names the core hands over are one list. assert.deepEqual([...REQUIRED].sort(), [...HELPER_NAMES].sort())