Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/2843-project-issue-ref.fixed.md
Original file line number Diff line number Diff line change
@@ -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).
2 changes: 1 addition & 1 deletion changelog.d/project-plugin.added.md
Original file line number Diff line number Diff line change
@@ -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).
2 changes: 1 addition & 1 deletion core/src/host/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 1 addition & 1 deletion core/src/host/day-key.js
Original file line number Diff line number Diff line change
@@ -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.
//
Expand Down
2 changes: 1 addition & 1 deletion core/src/host/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion core/src/host/plugins.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion core/src/host/project-host.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion core/src/host/work-dir.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion core/test/agents.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
2 changes: 1 addition & 1 deletion core/test/install.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion core/test/project-domain.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion core/test/run-bot-tools.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions core/types/flybot.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown>
helpers: Record<string, unknown>
Expand All @@ -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
Expand Down
6 changes: 3 additions & 3 deletions docs/subsystems/core-host.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<untrusted-message>` 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`. |
Expand Down
2 changes: 1 addition & 1 deletion docs/subsystems/plugin-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading
Loading