mirror of
https://github.com/lobehub/lobehub.git
synced 2026-09-20 04:56:13 +08:00
feat/quick-note-db-migration
844 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b9917c1894 | 🐛 fix(quick-note): preserve pinned document history revisions | ||
|
|
c1dfecbdd3 |
🔧 chore(release): refresh weekly candidate with six canary PRs (#19432)
Co-authored-by: Innei <i@innei.in> Co-authored-by: YuTengjing <ytj2713151713@gmail.com> |
||
|
|
f2a0901ef1 |
🐛 fix: harden bot messaging and channel setup (#19405)
* 🐛 fix: harden bot messaging and channel setup * 🐛 fix: retain bot messages across sender and replay races * 🐛 fix: retry deferred bot replay independently * 🐛 fix: preserve command turns and debounce owner lookup |
||
|
|
8171469b80 |
✨ feat: read Feishu documents and preserve rich-text images (#19401)
* ✨ feat: read Feishu documents and preserve rich-text images * 🐛 fix: bound Feishu markdown image scanning * 🌐 chore: refresh translations after rebase |
||
|
|
91a77ae585 |
✨ feat: support member mentions in workspace documents (#19402)
* ✨ feat: support member mentions in workspace documents * 🌐 chore: refresh generated translations * 🐛 fix: enforce knowledge base access for document notifications * ✅ test: include mention plugin in readonly editor mock |
||
|
|
f6660c1666 |
🐛 fix(goal): recover pipeline failures instead of escalating them to a person (#19377)
* 🐛 fix(goal): recover pipeline failures instead of escalating them to a person Supervision only acted on a failure that had already produced an errored operation. Dispatch failures, a dropped device link and a crashed verifier never take that shape — the run either never started or finished cleanly and something around it broke — so the whole class escalated to a human decision gate. In a live run these were the only three interruptions, all of them recorded as `Only a confirmed failed operation can be recovered`, and each one stopped a long-horizon goal until someone clicked retry. - Classify a failure by where it happened. An `agent-run` failure is still judged by its own error text; a `pipeline` failure recovers without one, because retrying re-uses authority the goal already granted. - Keep the human gates that matter: an explicit interruption, a limit stop, a cancelled Task, exhausted attempts, and anything reading as credentials, permission or spend. - Route `VERIFICATION_ERRORED_ERROR` to the verification recovery branch. A verifier that could not run never judged the delivery, so it is infrastructure rather than a verdict; without a branch it fell through to the gate. - Move the three Task error contract strings into `@lobechat/const/goal` so the side that writes one and the coordinator that matches on it cannot drift. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(goal): apply pipeline recovery on the status the Task actually holds Codex review caught the previous commit widening eligibility without widening the step that acts on it, so the fix was a no-op for its own target case. - The recovery compare-and-set expected `failed`. A pipeline failure leaves the Task `paused`, so the swap never matched, the incident was marked escalated and the goal fell back to the human gate anyway. Compare against the status revalidated inside the transaction instead. - A kickoff that threw before writing a run has no operation to key an incident on, and `reviewFailure` returned early on exactly that shape. Key those on the Task and its attempt so they are recorded, deduplicated and budgeted like any other incident. Both regressions are covered by tests that fail without these changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ♻️ refactor(goal): drop the half-built operation-less supervision path Codex review found two consequences of keying an incident on a synthetic id: `TaskRunnerService` leaves `totalTopics` unchanged when a retry also fails before writing a topic, so a repeated dispatch failure reused the first key and fell through to the gate anyway; and `recordProgress` settles an incident by locating the failed run's sequence, which a synthetic key can never match, so the incident stayed `retrying`, understated recovery metrics and could relabel a later recovery as unsuccessful. Both follow from incident identity and settlement being anchored on a real run. Making the operation-less case correct means redesigning that, which is its own change rather than a rider on a policy fix — and a half-built version is worse than none, because it writes incidents that can never settle. Restore the early return and leave that case on the human gate. What this PR fixes is unchanged and covers every interruption observed live: a settled run whose Task is left `paused` now recovers instead of escalating. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(goal): classify by the operation, not the Task status Review found the origin test read the wrong signal. `TaskLifecycleService` stores an ad-hoc run that fails as `paused` with an errored operation — the same Task shape a pipeline failure leaves — so requiring `failed` labelled real agent errors as pipeline failures and routed them around the credential and transport checks this PR claimed to keep. An errored operation now decides it, whatever the Task holds. Also bound the statuses supervision may restart to the two the coordinator actually routes to recovery. The previous commit compared the swap against whatever status the Task held, which would have accepted a terminal one; a completion recorded during a diagnosis must never be swapped back to `backlog` and rerun. Guarded in the policy and again at the swap itself. The misclassification has a regression test that fails without the fix. The terminal-status guard is defence in depth: it is not reachable through the coordinator, which does not route a settled Task to recovery, so its test pins the invariant rather than reproducing a failure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(goal): recognise recoverable dispatch failures instead of inferring them Review found a fifth state that looked like a pipeline failure and was not: a caller marking a Goal-linked Task `failed` after a clean run. Recovery would reopen it and spend more paid work despite an explicit terminal decision. That is a pattern, not a case. Inferring a recoverable failure from the *absence* of a run error is an open set: every state an actor can author arrives looking exactly like a dropped dispatch, so each round found another one. Replace it with one allowlist read off whatever error text exists, applied to both origins. Recoverable now means a recognised transport failure or a gateway code whose own message states the run never started, so a retry cannot duplicate committed work: `DEVICE_OFFLINE`, `DEVICE_CHANNEL_UNAVAILABLE`, `DEVICE_GATEWAY_UNREACHABLE`, `DEVICE_GATEWAY_RATE_LIMITED`. The codes that say retrying will not help (`DEVICE_GATEWAY_UNAUTHORIZED`, `GATEWAY_NOT_CONFIGURED`) or that the outcome is unknown (`DEVICE_RESPONSE_TIMEOUT`) stay with a person, as does an authored failure carrying an arbitrary error or none. `failureOrigin` goes away with the inference it existed to express. The three observed interruptions are still covered: they carried gateway codes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(goal): recognise a dispatch failure in either shape it is stored as Review found the allowlist read only one of the two forms a dispatch failure takes in `task.error`. The coordinator's own dispatch persists the raw gateway payload — confirmed against a live run, where the gate reason was `{"error":"DEVICE_OFFLINE","success":false}` — while the runtime's `finalizeHeteroDispatchError` persists the humanized headline, which contains no code. Matching codes alone therefore still opened the gate this PR exists to remove, on the path that finalizes through the runtime. Match both, derived from the one headline map rather than copied text, so the storage shape does not decide whether a person is needed. `HETERO_DISPATCH_ERROR_HEADLINES` is exported for that. `DEVICE_NOT_FOUND` is also called out as staying with a person: reconnecting or rebinding the device is not something a retry does. The regression asserts the actually persisted value through `humanizeHeteroDispatchError`, not an injected raw code, and fails without the headline match. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(goal): allow the gateway's own 5xx code into transient recovery Review found `DEVICE_GATEWAY_ERROR` missing from the allowlist. The gateway client emits it for any status at or above 500, and its headline states outright that nothing started on the device and the failure is usually temporary — which is the criterion the allowlist was written around. The fallback transport regex only covers 502-504, so an HTTP 500 dispatch failure matched nothing and still opened a human gate. Regression covers both persisted forms, the raw code and the humanized headline, and fails without the entry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(goal): never restart a Task an actor already settled Two ways a terminal decision could be overturned and paid work launched. - `TaskService.updateStatus` replaces `error` only when a new one is supplied, so an actor can move a pipeline-paused Task to `failed` and leave the transport text that paused it, which the allowlist then reads as a dropped dispatch. The activity log records assignee changes rather than transitions, so the author is not readable; require the errored run instead. Nothing the system does writes `failed` with a recoverable error — the watchdog's is `Heartbeat timeout`. - `TaskRecoveryCoordinator.recover` rejected only `running`, then swapped whatever status it found to `running` and spawned a run. A person completing or cancelling between the tick's read and the claim would have their decision overwritten. This path bypasses supervision entirely, so the policy guard did not cover it; bound the claim to the statuses that await recovery and report `settled` so neither call site opens a gate over somebody's own decision. The claim race predates this branch on the lease path; routing the verifier error here widened its reach, so both are covered by the regression. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(goal): claim only the paused state recovery was routed for Review found the shared recoverable set was the wrong bound here: it includes `failed`, so a person marking the Task failed between the tick's read and the claim still passed the guard and had their decision swapped to `running`. Both branches that reach this coordinator require `paused`, so that is the only status the claim may take, and the rollback releases to the same one. Coverage now spans a concurrent move to `completed`, `canceled` and `failed` across both the lease and verifier-error routes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ♻️ refactor(goal): give a Task's return to work one gate Three rounds of review each found another entrance through which automatic recovery could overwrite a decision somebody made, because no single place owned the question of who may put a Task back into work. Patching entrances found the next entrance; this collects the rule instead. `claimGoalTask` is now the only way a Goal moves a Task to `running` or `backlog`, and it states the invariant the three sites were getting right or wrong independently: claim against the status the decision was made on, never a fresh re-read. Losing the compare-and-set is the correct outcome — it means the row moved, so the decision is stale and belongs to whoever moved it. Dispatch already worked this way, which is why it was never implicated. The recovery coordinator re-read first and is now pinned to the routed `paused`. Supervision had the widest window of the three: a diagnosis runs for minutes, so the incident records the status it opened on and claims against that, rather than only catching a change since the current tick. Regression covers a Task moved to another recoverable status mid-diagnosis, which the status allowlist accepts and only the claim rule refuses. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(goal): escalate a diagnosis that cannot say what it opened on The compatibility fallback restored the race it was added beside: an incident persisted before `taskStatus` existed claimed against a later read, so a person moving the Task during such a diagnosis would have that undone. An incident with no opening status cannot show the row is still the one it decided about, so it escalates instead. A rolling deploy leaves at most the few mid-diagnosis, and a human gate is the right answer for them. Covered by a regression that strips the field the way an older release left it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(goal): ask who wrote a Task's status, not what its error says The previous guard read the error, and the error cannot tell these apart: when a run errors with a recoverable transport failure the lifecycle leaves an errored operation and a paused Task, and a person marking that Task `failed` without supplying a new one keeps the same text. Requiring the errored run therefore passed exactly the case it was meant to stop, and supervision would put an explicit failure back to `backlog` and spend more. An earlier commit asserted the author was not readable. That was wrong, and the comment saying so is gone: `status` is a tracked column, so `updateWithLog` records an actor on the transition, and `TaskService.updateStatus` documents that its absence is how system transitions opt out of the feed. That makes actor presence the discriminator this needed all along. `statusAuthoredByActor` reads the latest tracked transition, requires it to be the one that produced the current status, and both eligibility calls pass it. Regressions cover the helper's edges and the end-to-end shape: errored run, paused by the lifecycle, then failed by a person. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(goal): read the durable actor kind, and honour a pause a person made Two gaps in the provenance check, both confirmed against the code that writes it. - It read only the actor id columns. Those are `ON DELETE SET NULL`, and `taskActivityActor` writes `actorKind` into the payload for exactly that reason: "someone who is gone must not read as the system". An authored failure became recoverable once its author left the workspace. Read the kind first and keep the ids as the fallback for rows written before it. - Verifier-error recovery checked only that the Task was still `paused`. A person moving it `paused → failed → paused` keeps the routing error, because the status update replaces it only when a new one is supplied, so the final pause is theirs and the status alone cannot say so. It now asks the same question the supervisor does and reports `settled`. Covered by the tombstoned-actor shapes and by the manual round trip end to end. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d2caf84fdd |
✨ feat(acceptance): serve the collaboration table, and let a round introduce itself (#19393)
* ✨ feat(acceptance): serve the collaboration table, and let a round introduce itself The second of three layers. It stacks on the table PR and is called by the UI PR above it; on its own it adds an endpoint nobody invokes yet. `acceptanceComment` covers create, delete, list, resolve and an emoji toggle. Reads are addressed by acceptance id alone and the handler decides visibility, so a public share link works without a session; writes need the creator or a non-viewer member of the acceptance's own workspace. A missing right reads as NOT_FOUND rather than FORBIDDEN, which is what the bundle read already does. The read does the joining the page would otherwise do by hand: author profiles, membership status, round indexes for a comment's context run, attachment ids resolved to display URLs as the aggregate's owner, and reaction rows folded into the comment they answer. `lh acceptance run ingest` gains the round's own note. An agent writes `proposal.md` beside the report; ingest posts it as a comment carrying that round's id and signed by the agent, so a delivery introduces itself the way a pull request does. It is the last step and never fatal — against a server without the endpoint the round still publishes, minus the note. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(acceptance): scope comment agent and run/evidence references A comment carries three references the schema alone does not constrain: the agent it speaks as, the round it was written against, and the evidence tile it is pinned to. Access is resolved from `acceptanceId` only, and the foreign keys prove global existence rather than association, so any participant could name rows from an acceptance they cannot read — or sign a comment as somebody else's agent and expose its name, title and avatar through a public acceptance link. Gate `authorAgentId` through `assertAgentUsableBy` in the acceptance's own workspace, the scope the agent is being presented to, and verify that `contextRunId` and `anchor.evidenceId` resolve back to the target acceptance before insert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(acceptance): categorize the comment namespace for API key scopes The scope guard fails closed on any namespace missing from `TRPC_NAMESPACE_API_KEY_RULES`, and a coverage test makes sure a newly mounted router is categorized on purpose rather than silently denied. `acceptanceComment` follows the acceptance it hangs off: blocked for restricted keys, reachable with a full-access one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
aba9e8150c |
👷 build: migrate the test suite to vitest 5 (#19355)
* 👷 build: upgrade vitest to 5.0.0 Bump vitest and @vitest/coverage-v8 to 5.0.0 and migrate the configs for Vitest 4/5: drop the removed coverage.all option and replace poolOptions.forks.singleFork with maxWorkers/isolate. * 👷 build: shim jest-dom matcher types for vitest 5 Vitest 5 exposes Assertion<R, T> and no longer reads the jest.Matchers augmentation that @testing-library/jest-dom ships, so its matchers vanish from expect(). Re-declare them on the Vitest interface, and replace the removed describe.sequential with { concurrent: false }. * 👷 build: migrate the test suite to vitest 5 Vitest 5 (and @vitest/coverage-v8) replaces the esbuild dep optimizer with Rolldown, removed environmentMatchGlobs/coverage.all/poolOptions, forwards `new` to mock implementations, requires top-level vi.mock, and no longer resets automocks in restoreAllMocks. Migrate the root config to app/server projects with Rolldown optimizer options, shim the jest-dom matchers on Assertion<R, T>, and update the affected tests (constructor mocks, hoisted mocks, awaited assertions, explicit mock types, per-test mock defaults). * 👷 build: make the openapi knowledge-base FileService mock constructable tests/ was outside the per-area sweep; vitest 5 forwards `new` to the mock implementation, so the arrow implementation threw "is not a constructor". * 👷 build: point the CI blob-report artifacts at the vitest 5 output dir Vitest 5 writes blob reports to .vitest/blob instead of .vitest-reports, so the sharded jobs uploaded nothing and the merge jobs failed with ENOENT. Track the new directory in .gitignore too. * 👷 build: refresh the desktop lockfile for vitest 5 apps/desktop/pnpm-lock.yaml is committed and the desktop build action installs with --frozen-lockfile, so the vitest/override change has to be reflected in it. * 👷 build: keep the pre-vitest-4 coverage exclusions explicit Vitest 4+ ships an empty coverageConfigDefaults.exclude, so configs that spread it silently lost the default exclusions (node_modules, dist, *.config.*) and counted more files. Inline the previous list. * 🎨 style: format the migrated vitest configs * 👷 build: keep canary's new class mocks constructable under vitest 5 exploreGraph/dispatchAuthorization/scopedDevices (canary #19375, #19352) mock classes with arrow implementations; vitest 5 constructs the implementation when the mock is newed, so the arrows throw 'is not a constructor'. |
||
|
|
99fbd13ba0 |
🐛 fix(agent): accept a typed answer in a multi-select intervention question (#19399)
Submitting an AskUserQuestion card failed with a 500 whenever the user wrote their own answer in a multi-select question. Two separate defects stacked up. The answer was genuinely rejected. The client carries a "write your own" entry as one more string in the multi-select array, and the server marks every AskUser question `allowCustomAnswer: true`, but the multi branch of `hasValidAnswers` whitelists the option ids unconditionally — only the single-select branch below it honors the flag. Any typed answer therefore failed the whole submit, and the user had no way to tell which question was at fault. The multi branch now reads the same flag. The rejection then surfaced as an Internal Server Error. A contract failure is a statement about the request, so map the intervention error codes to their tRPC equivalents at the three resolution procedures: invalid payloads become BAD_REQUEST, lost races and stale revisions CONFLICT, and an unavailable review FORBIDDEN. Unrecognized failures are passed through untouched so a genuine server fault still reads as one. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
f4a39da7d9 |
✨ feat(goal): let exploration revise a flawed protocol instead of only branching (#19375)
* ✨ feat(goal): let exploration revise a flawed protocol instead of only branching The exploration planner could only `expand` or `verify`, so when an experiment measured the wrong thing its only move was a sibling that inherited the same flawed instrument. It also had no way to tell which experiments a previous turn authored, so it could not see that its last branch had made things worse. - Add a `revise` action that re-runs one experiment with a corrected protocol inside the parent's container, without consuming an experiment slot. - Bound revisions per target via `derived_from` edges so a planner cannot keep polishing one instrument instead of changing the question. - Pass `derivedFromId` to the planner so it can compare a derived experiment against its parent before repeating that direction. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(goal): separate a protocol correction from an ordinary branch Codex review found three ways the revision path and the expansion path were conflated, because both write a `derived_from` edge. - The revision cap counted every `derived_from` edge at the target, so an experiment with two ordinary branches was refused its first correction. Count only edges a task sourced, which is what a correction is. - A standalone correction read back as an experiment: it spent an experiment slot and could then be revised as a fresh target with an empty budget, an unbounded chain. Exclude corrections from the experiment selector on both the planner and the apply side so the two see the same graph. - A dispatched correction resolved its provenance through its container, which carries the container's own branch parent, so the run received the results of an older experiment. Prefer the node's own provenance edge. The lookup moves to `provenanceParentId` in `@lobechat/utils/goalGraph` next to the two new predicates, so the rule is unit-testable rather than inline in a 600-line dispatch path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(goal): keep a correction's evidence and stop a spent allowance pausing the goal Two consequences of the previous commit, both found in review. - Excluding corrections from the experiment selector also hid their results. `experimentResults` only walked containment, so a standalone correction's findings were folded into neither itself nor the experiment it corrected, and the next exploration turn evaluated pre-correction evidence only. Gather an `experimentScope` that includes the corrections aimed at a node, and use it for produced Work versions too. - A spent revision allowance threw, and `exploreGraph` treats a throw as a planner crash: it paused the whole goal, and resuming could reach the same choice again because nothing told the planner the allowance was gone. Return it as an outcome that re-plans, and put `revisionsRemaining` in the planner input so the choice is observable before it is made. `MAX_PROTOCOL_REVISIONS` moves next to the revision predicates in `@lobechat/utils/goalGraph`, since both the model and the planner input need it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(goal): give corrections a container, a released lease and a documented contract Four more from review, all consequences of the correction path being bolted onto a graph shaped for branches. - A spent allowance returned without clearing the checkpoint, so the re-plan it asked for was refused by the next claim until the lease timed out. Release it like every other terminal path. - A correction of an uncontained seed was another root task: invisible to the planner, unable to inherit the previous correction's Work, and read back as a slot-consuming experiment by an older release. Require an experiment container and let those goals expand first, which removes all three at once. Both apply paths now gather `experimentScope`, so a correction's Work carries forward. - `revisionsRemaining` reports zero for a node that cannot hold a correction, rather than inviting a choice the apply step would refuse. - `EXPLORATION.md` documented two actions. It now covers the third, the slot-free guarantee, the per-experiment bound and why corrections are serial. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(goal): record corrections on their own edge kind Review found `derived_from` was the wrong carrier. It is generic provenance that predates this feature, `goal.addEdge` accepts it, `createEdge` puts no task-source restriction on it, and an existing dispatch test authors exactly that shape. Treating any task-sourced one as a correction therefore reinterpreted hand-authored provenance: those tasks vanished from planner history and slot counting while their results were attributed to another node. Corrections now use a dedicated `revises` kind, written only by the coordinator and deliberately absent from the public `addEdge` vocabulary, so the discriminator is unambiguous and nothing existing changes meaning. The column is plain text, so no migration is involved. Also stop replanning on a rejected revision. Returning `advanced` re-invoked the planner with the same graph and the same `revisionsRemaining: 0`, which could spend an advance's whole tick budget repeating one question; the advance now ends and the goal stays running for the next wakeup. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 📝 docs(goal): note the dedicated revises edge in the exploration contract Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(goal): park a goal whose planner asks past a spent revision allowance Review found ending only the current advance left the goal running with an unchanged graph, so every sweep could buy another identical planning call indefinitely, and enough such goals would crowd the sweep's bounded scan. The previous round's `advanced` had the same defect inside one advance. Park it instead, the way an exhausted experiment budget already does: `pausedBy: 'exploration_revision_limit'`, a recorded status transition naming the target and the count, and `no_progress` carrying that reason. Resuming replans. The new reason is deliberately not treated as budget-stopped, so raising a budget does not silently reopen a goal that needs a different plan. Also documents the rollback shape for a queued correction: the previous coordinator resolves provenance through the containing experiment, so a rolled back deployment dispatches it with that experiment's branch parent instead of the one it corrects. The corrected protocol lives in the Task description and survives, so the loss is context rather than a wrong instruction; the note says to drain queued corrections first if that matters. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d26542b423 |
✨ feat(acceptance): let an author delete a flow the delivery no longer follows (#19388)
A published flow was permanent. Authoring a journey takes a few tries, and every try stayed on the acceptance forever: an abandoned first draft still rendered as its own root graph carrying its own never-executed checks, so the checklist filled with rows nobody would ever verify and the reader could not tell which graph the delivery was actually being checked against. `lh acceptance flow delete <acceptanceId> --flow <flowId>` removes one, and only while it has no verified history. A settled round renders from the snapshot it froze, so deleting a graph it ran would outlive the map explaining its results; that is refused, as is deleting a graph another flow invokes as a subflow. A draft round has nothing to preserve, so the flow's snapshot and plan items are stripped from it and the remaining items re-indexed, leaving the ledger as if the flow had never been planned. The check assets the nodes pointed at are reusable on their own and stay. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
aacb88fba7 |
✨ feat(heterogeneous-agent): support Devin CLI as a heterogeneous agent (#18554)
* ✨ feat(heterogeneous-agent): support Devin CLI * 🐛 fix(heterogeneous-agent): reuse cached Devin authentication * ✨ feat(heterogeneous-agent,cli): default Devin ACP to bypass permission mode - Add `permissionMode` to `DevinAcpSessionOptions` and `SpawnAgentOptions`. - Update `buildDevinAcpArgs` to place `--permission-mode` before the `acp` subcommand, since it is a global `devin` flag. - Default the CLI `lh hetero exec` to start Devin with `permissionMode: 'bypass'` so headless connected-device runs avoid blocking on permission prompts. - Support user-supplied `--permission-mode` in `extraArgs`, which takes precedence and is moved to the correct position. * ✨ feat(heterogeneous-agent): set Devin ACP session mode via session/set_config_option The `--permission-mode` global flag only affects the Devin CLI process; the ACP session's mode still defaults to `accept-edits`, so shell/edit tools still trigger `session/request_permission` and show askUserQuestion. After `session/new` or `session/load`, call `session/set_config_option` with `configId: 'mode'` and the resolved permission mode to actually put the ACP session into bypass (or user-override) mode. The resolved mode is derived from the built argv so an explicit `--permission-mode` in extra args still wins over the default. Add regression tests for new sessions, resumed sessions, and user override. * ✨ feat(heterogeneous-agent): apply Devin ACP initial model via session/set_config_option Previously `initialModel` was only used as a fallback when `session/new` returned no model. Devin's `session/new` always reports a default model (e.g. `swe-1-7-medium`), so a user-selected `glm-5-2` was ignored and the default was used. Add `applyInitialModel()` to resolve the requested model against the `configOptions` catalog and call `session/set_config_option` with `configId: 'model'`. It matches by raw model value and by normalized human-readable name (so 'glm 5.2' maps to 'glm-5-2'). When only a raw `initialModel` is provided and the catalog is unavailable (e.g. resume), it attempts to set the value directly. Also preserve the updated model after setting the permission mode so the final `devin_session` event reports the selected model. Add regression tests for model selection, label matching, default preservation, and combined permission mode + model setup. Update the `spawnAgent` fake to expose a 'sonnet' option for the new behavior. * ♻️ refactor(heterogeneous-agent): stop injecting --permission-mode bypass CLI flag Devin ACP permission mode is now applied through `session/set_config_option` (configId: 'mode'), which is the only reliable way to change the active ACP session mode. The global `--permission-mode` CLI flag before the `acp` subcommand does not change the ACP session's effective mode. - `buildDevinAcpArgs` no longer injects a default `--permission-mode`. - It still moves a user-supplied `--permission-mode` in `extraArgs` before the `acp` subcommand for backwards compatibility. - `DevinAcpSession.resolvedPermissionMode` falls back to `options.permissionMode` so the default CLI `bypass` is still sent via ACP. - Updated tests to expect `acp` without the default flag and assert the `session/set_config_option` mode request. * fix(devin): do not default to bypass permission mode and protect selected model from mode response - Remove the hard-coded `permissionMode: 'bypass'` default for Devin in `lh hetero exec`. Users can still pass `--permission-mode` via `--agent-arg` when needed. - Stop using the `session/set_config_option` mode response to overwrite the model resolved from `applyInitialModel`. Permission-mode negotiation can return config-options whose `model` value is stale/default, causing a freshly selected model (e.g. `glm-5-2`) to be replaced with the Devin default (`swe-1-7`) on `stream_start`. - Update CLI tests to no longer expect the bypass default. Refs LOBE-xxx (feat/devin-cli). * fix(devin): keep default bypass permission mode while protecting selected model Re-introduce the default `permissionMode: 'bypass'` for Devin ACP so headless connected-device runs still do not block on permission prompts. The previous change that removed it was too aggressive; the real bug is that the `session/set_config_option` mode response was overwriting the selected model. That guard in `DevinAcpSession` remains in place. Refs LOBE-xxx (feat/devin-cli). * ✨ feat(heterogeneous-agents): capture and emit Devin ACP usage as turn_metadata - Forward `session/prompt` result `usage` from `DevinAcpSession` into the `devin_prompt_completed` synthetic event. - Add generic ACP `Usage` parsing to `TraeAcpAdapter`, plus `usage_update` handling with a protected `extractUsageFromUsageUpdate` hook. - Override `extractUsageFromUsageUpdate` in `DevinAcpAdapter` to parse `_meta.cognition.ai/{input,output,cachedRead,cachedWrite,thought}Tokens`. - Emit `step_complete` with `phase: 'turn_metadata'` so the frontend reducer writes `usage` (and optional `cost`) onto the assistant message. - Add `UsageData.cost` to mirror `MessageMetadata.usage`. - Add `devinAcp.test.ts` covering prompt-result usage, `usage_update` _meta, and fallback paths. * ✅ test(heterogeneous-agents): add missing request params type in spawnAgent test fixture * 🐛 fix(heterogeneous-agent): cancel device-side CLI process on user stop Previously only remote-task hetero types (openclaw / hermes) triggered a `cancelHeteroTask` dispatch to the device when the user clicked Stop. Local-cli types (claude-code / codex / devin / trae / qoder) that also run on a remote device via `dispatchAgentRun` were silently skipped — the UI showed cancelled but the CLI process kept running. Two fixes: - Server `interruptTask`: drop the `isRemoteHeterogeneousType` guard so any hetero type with a deviceId in runningOperation triggers the cancel dispatch. - Desktop `spawnLhHeteroExec`: register the spawned child process into `platformTasks` via a new `onChildSpawned` callback so `cancelHeteroTask` can find and kill it by operationId. Also add `detached: true` so the process-group kill reaches the CLI without signalling the desktop app. * 🐛 fix(cli): cancel lh connect device CLI process on user stop The previous cancel fix only covered the desktop device path. When a local CLI agent (devin/claude-code/codex/…) is dispatched through `lh connect`, the spawned `lh hetero exec` child was never registered into the task registry, so `cancelHeteroTask` dispatched from the server returned "No task found" and the remote process kept running. - `agentRun.ts`: spawn with `detached: true` and register the child PID into `taskRegistry` on spawn, keyed by operationId. Remove the entry on exit only if the exiting PID still owns the task. - `taskRegistry.ts`: widen `TaskEntry.agentType` from `'hermes' | 'openclaw'` to `string` so local CLI agent types can be persisted. - `heteroTask.ts`: `cancelHeteroTask` now signals the whole process group via negative-PID kill (Unix) or `taskkill /T /F` (Windows), with a 2s SIGINT→SIGKILL escalation — mirroring the desktop path. * 🐛 fix(cli): add cancel-trace logging to diagnose device CLI stop path Add [cancel-trace] logs at every hop of the cancel path: - server interruptTask: logs when dispatching cancelHeteroTask, when condition is not met (with runningOp/targetOperation details), and the dispatch result/error - agentRun.ts: logs when task is registered into taskRegistry, and when it is removed on exit (or skipped due to stale PID) - heteroTask.ts cancelHeteroTask: logs when received, when task is found or not found, and each kill attempt * 🐛 fix(server): seed deviceId/heteroType into runningOperation for local CLI hetero agents Local CLI hetero agents (devin / claude-code / codex / …) dispatch to a device via `dispatchAgentRun`, but `childOperation` only embedded `deviceId`/`heteroType` for remote platform agents (openclaw / hermes) guarded by `isRemoteHetero`. Without these fields in `topic.metadata.runningOperation`, `interruptTask` could not find the target operation and never dispatched `cancelHeteroTask` — so clicking Stop in the web UI did nothing for `lh connect` devices. Patch `runningOperation` with the resolved `dispatchDeviceId` and `heteroType` after a successful local CLI hetero dispatch, handling both root and child (group supervisor) operation shapes. * 🔥 chore: remove [cancel-trace] debug logs Clean up the temporary diagnostic logs added to trace the cancel path. Keep only the production-level `interruptTask` and `execAgent` logs that were already present or are useful for long-term observability. * 🐛 fix(heterogeneous-agent): preserve cancel lifecycle state * ♻️ refactor(heterogeneous-agent): simplify Devin ACP integration * 🐛 fix Devin intervention review flow * 🐛 fix heterogeneous agent cancellation * 🐛 fix heterogeneous agent cancellation confirmation * 🐛 fix(heterogeneous-agent): preserve Devin permission option IDs * 🐛 fix: resolve CI failures after rebase * 🐛 fix: route Devin verification and permissions * 🧪 test(cli): isolate agent run cancellation registry * 🐛 fix(cli): cancel full live agent process group |
||
|
|
3b0d29cd54 | 🐛 fix: harden device connection presence (#19352) | ||
|
|
30005de789 | 🐛 fix: prevent stale heterogeneous finish snapshot rollback (#19353) | ||
|
|
7be9722ae1 | 🐛 fix: restrict agent share rollout checks to creators (#19380) | ||
|
|
2d481065b5 | 🐛 fix: support Serverless indices without internal UUIDs (#19381) | ||
|
|
c17b9a9388 |
🐛 fix(acceptance): keep flow zoom on node click and stop draft rounds from inflating (#19350)
* 🐛 fix(acceptance): keep flow zoom on node click and stop draft rounds from inflating Clicking a node opens the step panel beside the canvas. The narrower canvas made the flow graph refit, discarding the zoom the user had just set. The viewport is now left alone, and the picked node is panned back at the same zoom only when the panel would hide it. An unexecuted round also forked a new version on every edit: `publish` left the planned round's snapshot behind, `flow plan` inserted another round, and `ingest` chained yet another one, so a ledger could reach round 4 before a single check had run. A round now stays a draft until its first result: `publish` refreshes every draft round in place, `flow plan` reuses the open draft, and `attachRun` folds a detached harness run into it. Both round selectors label a draft "Draft" instead of giving it a number, and the acceptance skill documents the same contract. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(acceptance): fold only into the newest round and keep a replay pinned Review follow-up. `attachRun` searched the whole ascending chain for a draft, so an older draft left behind by a `--from-run` replay absorbed an incoming run even after a newer round had executed. Only the newest round is the open draft now; anything behind it is an abandoned ledger position and a new round is chained instead. A replay is also excluded from the draft paths. It is pinned to the definition it replays, so refreshing it on the next `publish` would have silently swapped in the live graph and destroyed what the replay exists to re-run. `flow plan --from-run` records `metadata.replayOfRunId`, and the shared `isDraftVerifyRun` predicate treats such a round as numbered rather than open. Also restores the import order in the checklist inventory, which failed lint. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4bf05e2499 |
🐛 fix(bot): surface budget scope and headroom in bot failure replies (#19336)
* 🐛 fix(bot): surface budget scope and headroom in bot failure replies Carry the budget context of an InsufficientBudgetForModel error from the runtime lifecycle event through the hook payload into the bot reply, and render a scope-aware message (member allowance vs. workspace pool) with the remaining and required credits instead of the generic "Agent Execution Failed". LOBE-13726 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RKPxmeT4DrAdKP3RUCYzgp * 🌐 style: update i18n Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RKPxmeT4DrAdKP3RUCYzgp * 🔒 fix(bot): stop quoting the billed allowance in budget replies Runs mentioned from a shared channel are billed to the bot owner, so a reply that quoted availableCredits / requiredCredits published the owner's balance to every channel member. Keep the scope-aware copy and drop the amounts line; a regression test asserts no figure is rendered. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RKPxmeT4DrAdKP3RUCYzgp * 💬 fix(bot): address the bot owner and split plan-limit copy in budget replies Runs are billed to the bot owner, not to whoever mentioned the bot, so the budget copy no longer calls the reader the payer ("your budget") and points at the bot owner / workspace admin instead. FreePlanLimit and SubscriptionPlanLimit get their own copy (the allowance itself is exhausted, so the fix is the plan, not a cheaper model), and the budgetTypeAtError scope refinement now applies to all three admission codes since the workspace gate throws SubscriptionPlanLimit when a run can't be priced upfront. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RKPxmeT4DrAdKP3RUCYzgp * 💬 fix(bot): cover both meanings of SubscriptionPlanLimit in the reply Per the error taxonomy the code fires when the paid-plan allowance is reached OR when the plan tier does not cover the requested model, so the copy no longer claims the credits are spent and offers upgrade / top-up / custom API as the remedies. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RKPxmeT4DrAdKP3RUCYzgp * 👷 build: serialize build scripts so editor postinstalls stop racing on lexical Three peer-variant copies of @lobehub/editor patch the same hoisted lexical files in postinstall; run concurrently they read each other's half-written output and fail with "Patch context mismatch". Only the allowBuilds packages run scripts, so childConcurrency: 1 costs seconds. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RKPxmeT4DrAdKP3RUCYzgp --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
dac6eb0fdc | 🐛 fix: attach registered Works to the final assistant reply (#19345) | ||
|
|
20f16ef4cd |
🐛 fix(goal): create experiments only for planned hypotheses and fail dead dispatches (#19339)
* 🐛 fix(goal): create experiment containers only for planned hypotheses Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * 🐛 fix(task): fail the kickoff when execAgent reports a dispatch failure Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * ✅ test(goal): assert the v5 decomposition prompt cohort Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
6261fa7f84 | 🐛 fix: surface sandbox recreation in tool results (#19329) | ||
|
|
83c5dbadfd |
✨ feat(goal): orchestrate durable goal planning through main agent (#19277)
* ✨ feat: add opt-in goal supervision for interrupted tasks * ✨ feat(goal): give supervisor scoped investigation and recovery tools * 🐛 fix(goal): confirm device execution exit before releasing tasks * ✨ feat: run Goal planning through a durable CLI main Agent * 🐛 fix: carry Task review feedback into Goal planning * 🐛 fix: retain main agent verification handoff in final goal task * 🐛 fix(goal): bind main agent planning to the goal creator * ✨ feat(goal): expose main agent execution history * 💄 style(goal): label main agent entry as supervision progress * 🐛 fix(goal): open supervision in the collapsed conversation panel * 🐛 fix(goal): address operation auth and supervision review findings * 🐛 fix(goal): show read-only supervision in the goal language * 💄 style(goal): remove manager planning summary row * 🚨 fix(test): drop unused userEvent import in ApiKey test Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
33ba35e6cb |
💄 style(apikey): redesign API Key management (#19328)
- List: description under the title, monospace masked key (prefix + last 4), scope summary tag (full access / N scopes), disabled/expired status, relative last-used time, and a row "…" menu (view details / delete) replacing the trailing chevron - Create modal: expiration presets (7/30/60/90/180 days, 1 year, never, custom date) with a guard against submitting "custom" without a date; after creation the modal shows the full key with a copy button - Server: createApiKey returns the plaintext key once (createWithPlaintext) so the create flow can reveal it; list/detail reads are unchanged Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
d3c25f2a95 |
🐛 fix(bot): read Feishu chat history and decode WeChat voice (#19323)
* 🐛 fix(bot): read Feishu chat history, decode WeChat voice and gate personal-file visibility toggles - Feishu/Lark (LOBE-13803): inject the current `{ platform, channelId }` into the bot platform prompt so `readMessages` no longer asks the user for a group ID; explain `230027` and friends with the exact scope to add (`im:message.group_msg`); surface Lark `permission_violations` in API errors; map unicode reactions to Feishu `emoji_type` and track `reaction_id` so step swaps replace instead of stacking; decide the no-@mention exemption from real chat membership (`user_count`/`bot_count`, fail closed) instead of who has spoken. - WeChat (LOBE-13407): decode SILK voice to WAV on download via `silk-wasm` so the attachment is playable in the browser and usable by audio models. - ResourceManager: only offer "Make private" / "Publish to workspace" for the creator's own files inside a workspace; personal-mode rows default to `visibility = 'public'` and were wrongly offered the toggle. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V5Mi6LJCFiMnej3MPAjMpJ * 🐛 fix(feishu): read the newest history page and bound solo-chat cache staleness Address the Codex review on #19323: - `readMessages` and the chat-SDK `fetchMessages` asked Feishu for history without `sort_type`, which defaults to ascending — in any chat past the page cap the model got the chat's oldest messages instead of what was just said. Request `ByCreateTimeDesc` (kept on every cursor page) and flip the page so the documented chronological order still holds; `forward` maps to ascending. - A cached "solo" verdict kept the bot answering every unmentioned message for 10 minutes after someone joined the chat. Positive verdicts now expire after 60 s; only "shared" verdicts keep the 10-minute TTL. - Without agent-runtime Redis the verdict was never cached, costing a blocking Feishu request per inbound message. Fall back to a bounded process-local cache with the same TTLs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V5Mi6LJCFiMnej3MPAjMpJ * 🐛 fix(bot): skip membership lookups for addressed messages and keep the reaction id until deletion succeeds Second Codex round on #19323: - `onSubscribedMessage` only consults `isSoloBotConversation` when a DM or @mention has not already settled the question, so a lapsed cache never puts a platform round-trip in front of a direct message. - The Feishu final reaction clear forgot the tracked `reaction_id` before the remote delete ran; a transient failure left the reaction visible with nothing to retry with. Delete the tracker entry only after removal succeeds. - Fix the `result.messages` possibly-undefined type error CI caught in `service.test.ts`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V5Mi6LJCFiMnej3MPAjMpJ * 🐛 fix(bot): keep every placed Feishu reaction id until removal succeeds and scope Slack history injection Third Codex round on #19323: - Feishu reaction tracker keeps a list of ids per message, in Redis or a bounded process-local cache when agent-runtime Redis is absent (local runs used to stack every step reaction). A swap that adds the next reaction but fails to remove the previous one keeps both ids; the final clear retries removal in place before handing leftovers back to the tracker, since neither the bridge nor the queue callback retries it. Legacy bare-id keys still read. - `chatComposition` shares the same bounded memory cache helper. - New optional `PlatformClient.extractConversationId`: Slack declines for reply threads, so the injected current conversation never points `readMessages` (channel history only) at the parent channel of a thread. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V5Mi6LJCFiMnej3MPAjMpJ --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
679380bf56 |
⚡️ perf(agent-runtime): stop persisting step events to the reader-less agent_runtime_events list (#19321)
Every step LPUSHed its full event array (all streamed text/reasoning deltas plus tool events, with finalState snapshots measured at 133KB per entry) into agent_runtime_events, trimmed to 200 entries with a 2h TTL — and nothing in the codebase, the closed-source cloud layer, DC, or the agent-gateway ever read the key back. The same events already reach clients through the live stream publish and land durably in the operation trace. Dropping the write removes a full second copy of every streamed byte from Redis bandwidth, the largest single contributor to state-store storage growth, and 3 commands per step. Existing keys expire via TTL within 2h. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e9b1d082f3 | 🐛 fix: honor user media capabilities in server agent runs (#19296) | ||
|
|
065cc132d8 |
🐛 fix(goal): prevent duplicate decomposition with planning leases (#19307)
* 🐛 fix(goal): fence duplicate decomposition with planning leases * 🐛 fix(goal): protect planning leases and guard legacy takeover |
||
|
|
e580b9f85a |
🐛 fix(acceptance): preserve check history when regrouping (#19306)
* 🐛 fix(acceptance): group plans by business tasks and follow flow order * 🐛 fix(acceptance): preserve check identity and history when regrouping |
||
|
|
45e923e302 |
✨ feat(workspace): mirror account settings, inject workspace context, keep acceptance in scope (#19299)
* ✨ feat(workspace): mirror account settings, inject workspace context and keep acceptance in scope - LOBE-13896: mirror the account-level settings tabs (profile / appearance / hotkey / messenger / advanced / labs / about) under `/:workspaceSlug/settings` and regroup both settings sidebars (Account → Personal/Workspace → … → System → Developer) - LOBE-13895: add `WorkspaceContextInjector` so the system prompt carries the app origin and the workspace name/slug, with slug-prefixed link rules, on both the server runtime and the client context pipeline - LOBE-13898: open a task's acceptance in the side panel instead of navigating to the workspace-less `/acceptance/:id` page, which dropped the slug and flipped the app back to personal scope Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012YBAZ3KE8AknkvY1ZDKJJy * 🐛 fix(workspace): keep the workspace name out of the prompt and register the mobile hotkey route - only the validated slug is injected into `<workspace_context>`; the admin-controlled display name never reaches the system prompt - mirror `/:workspaceSlug/settings/hotkey` in the mobile router so the allowlisted path resolves there too Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012YBAZ3KE8AknkvY1ZDKJJy * 🐛 fix(prompts): trim trailing slashes without a polynomial regex CodeQL flagged `/\/+$/` on the app origin as polynomial on long runs of '/'; a character loop is linear and equivalent. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012YBAZ3KE8AknkvY1ZDKJJy * 🐛 fix(prompts): scope the workspace link rules to in-app links The link rules forbade lobehub.com outright, which also stopped the model from linking the LobeHub homepage in ordinary chats. They now apply only to self-composed links into the app. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012YBAZ3KE8AknkvY1ZDKJJy * ✅ test(task): cover the acceptance-row click with a hook regression test Extracts the panel-opening behaviour into `useOpenAcceptanceInPanel` so the LOBE-13898 fix is asserted in a hook test: it toggles the task panel and opens the acceptance, and never routes to the workspace-less `/acceptance/:id` page. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012YBAZ3KE8AknkvY1ZDKJJy * ✅ test: repair the two canary-red suites so Test CI can pass - `ApiKey.test.tsx`: `@lobehub/ui@5.40.2` (published 2026-09-08, bumps `@base-ui/react` to 1.8.0) no longer toggles the dialog checkbox on a synthetic Space key or a bare click on the `role=checkbox` span; click the wrapping label instead, which is what a real pointer hits (LOBE-13941) - `localization.test.ts`: drop the `content` field that #19293 removed from `BuiltinSkillManifest` (LOBE-13942) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012YBAZ3KE8AknkvY1ZDKJJy --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
13ada65ad9 | ✨ feat(fts-search): safe per-entity Elasticsearch mapping migrations (#19186) | ||
|
|
a5e74a9eb4 |
⚡️ perf(spa): cut the editor runtime, ajv and database models from the boot path (#19294)
* ⚡️ perf(spa): cut the editor runtime, ajv and database models from the boot path Four package-level import edges put ~1.5 MB raw on the first screen: - GlobalProvider/Editor.tsx imported @lobehub/editor/react, and rolldown does not shake that entry down to EditorProvider, so lexical, yjs and fuse landed in the entry chunk. The provider is reached by path until the package exposes a provider-only entry. - @lobechat/types re-exported the ajv-backed AgentGraphSchema through the barrel that every store touches; it now lives on the @lobechat/types/agent/graph subpath and the agent barrel re-exports types only. - @lobechat/context-engine's root barrel dragged the database models barrel (drizzle) in through AgentDocumentInjector; the app reaches the ./agent-document subpath and the injector reads loadPolicy directly. - packages/locales bundled locales/en-US/*.json on top of the default sources it mirrors; the overlay now applies only in development. Web production build: first-screen gzip 2299 KB -> 1968 KB. Claude-Session: https://claude.ai/code/session_01QKr8CsFLYmrgqbRTSipTNH * 🐛 fix(vite): keep one EditorContext in dev for the path-imported provider Claude-Session: https://claude.ai/code/session_01QKr8CsFLYmrgqbRTSipTNH * 🐛 fix(locales): refresh consumers after the dev English overlay lands Claude-Session: https://claude.ai/code/session_01QKr8CsFLYmrgqbRTSipTNH |
||
|
|
29acee69f6 |
✨ feat(desktop): added Computer Use with auv (#19051)
|
||
|
|
812b39fa8e |
🐛 fix(work): let users remove orphaned document works from the card (#19275)
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
e7c0a831f7 | ✨ feat(goal): add set-agent and restart commands (#19181) | ||
|
|
2c29ff0e44 |
✨ feat: add reusable check assets and grouped acceptance flows (#19268)
* ✨ feat: add reusable acceptance user journey flows * 📝 docs: define reusable check asset schema * ♻️ refactor: persist acceptance checks as reusable assets * 📝 docs: remove acceptance design markdown * ♻️ refactor: simplify flow definitions and isolate multi-flow navigation * ✨ feat: compose acceptance flows in a grouped canvas * 🐛 fix: address acceptance flow review and CI failures * 📝 docs: clarify flow completion requirements |
||
|
|
8fbb091050 |
✨ feat(task): record assignment, status, priority and automation changes in the activity feed (#19258)
* ✨ feat(task): record assignment changes in the task activity feed Assigning a task to a member or an agent only rewrote a column on `tasks`, so the detail page's activity feed had no way to show who moved it or when — a reader saw "created the task" and nothing else, however many times the task had changed hands. Every write path already funnels through the `task.update` procedure (the member/agent pickers, the `editTask` builtin, the CLI), so the diff between the pre- and post-update assignee columns is taken there and appended to `task_activities`. Only a slot that actually moved is written, so re-saving the same assignee stays silent. Unlike the assignment notification beside it, the write is awaited rather than deferred: the client refetches the detail as soon as the mutation resolves, and a row written after the response would miss that refetch. `updateSchema` gains an `actorAgentId` used solely for attribution, so an agent reassigning through its tool is recorded as the agent rather than the human whose session it borrowed. On the render side `ActivityAuthor` is extracted so the actor and the assignment target share one avatar/name/tag treatment, and `AssignmentRow` reads as "<actor> assigned the task to <target>" / "<actor> removed the agent". A target whose account no longer resolves keeps a stub author and renders as deleted, because collapsing it to null would turn a reassignment into a removal. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015wKZMihFpqsChKSb5YR23P * 🌐 chore(i18n): sync generated locales Runs `bun run i18n` so the 16 generated locales pick up the task assignment activity strings instead of falling back to English until the daily workflow catches up. The run also flushes locale drift that had accumulated for other namespaces (auth / device / modelProvider / models / providers) and realigns two hand-edited en-US / zh-CN values with their source in `packages/locales/src/default/chat.ts`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015wKZMihFpqsChKSb5YR23P * 🔒 fix(task): make assignment attribution unforgeable and atomic Addresses three findings from review on the assignment activity log. **Attribution came from the request payload.** `updateSchema.actorAgentId` was public input, so any authorized client could name an arbitrary agent and have the durable feed claim that agent made the reassignment — while suppressing the real user. The field is gone; the agent now rides `AuthContext.actingAgentId`, set only by a server-side `createCaller` from an already-authorized run context, following the contract `spendOrigin` already established. **The diff was read outside the write.** The previous assignee was read by the router before the update and recorded after it, so concurrent reassignments of one task could both observe the same origin: a racing A→B and A→C persist A→B then B→C while the log claims A→B and A→C, losing the middle state, and the rows could be ordered differently from the writes. `updateWithAssignmentLog` now derives the previous value from a `FOR UPDATE` read and appends the events in the same transaction as the update. **One writer bypassed the recorder.** `TaskRunnerService` persists the inbox agent when an unassigned task first runs, straight through `taskModel.update`. The assignee chip visibly changed with nothing in the feed to explain it; that write now goes through the same path, with neither actor column set since nobody requested it — the feed renders those as the system. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015wKZMihFpqsChKSb5YR23P * 🌐 fix(task): make assignment attribution reach the router, and let locales order the sentence Round-2 review follow-ups on the assignment activity log. **The attribution fix was inert.** The caller built with `actingAgentId` in the runtime factory is replaced by `ensureModels()` before any exported method runs, and the replacement omitted it — so every agent-driven reassignment was still recorded as the session user. The previous test missed it by building `createTaskRuntime` directly with a ready-made caller, never exercising the registration lifecycle; the new test drives `taskRuntime.factory` and asserts on the caller the wrapped method actually uses. **The row could not be translated.** Actor, verb and target were three DOM nodes in a fixed order, so a verb-final language could not put the target before the verb — Japanese rendered as a complete sentence with the target stranded after it. The four verb strings are now full sentence templates with `<actor/>` / `<target/>` placeholders rendered through `<Trans>`, so each locale decides where the participants go. The generated locales had to be dropped and retranslated: the keys kept their old fragment values otherwise, since the script only fills in missing ones. **The agent prompt claimed false provenance.** An actorless row is the runner's system fallback, which the feed labels "System" but the prompt rendered as "someone". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015wKZMihFpqsChKSb5YR23P * 🐛 fix(task): tell absent, invisible and unnamed participants apart Round-3 review follow-ups. All three findings come from the same mistake: collapsing distinct identity states onto one fallback. **An invisible agent was credited to the system.** `getAgentAvatarsByIds` filters by `ownership()`, so another member's private agent does not resolve for the current viewer. The actor lookup returned `undefined`, and both the row and the prompt reserve an absent author for the system — so an agent's reassignment was durably shown as a system action. The actor now keeps a stub carrying `unresolved: true`, as the assignment target already did. **A live participant could borrow a deletion label.** `users.full_name` and `agents.title` are both nullable, so a resolved row with an empty name fell through `||` onto "System" or "a deleted agent". Display names now fall back to the other column each query already returns (`title || name`, `fullName || username`), and a resolved-but-nameless participant renders as "Unnamed" rather than as gone. **Two formatters dropped the event.** The `viewTask` tool prompt and the CLI task detail have their own activity branches and neither knew about assignments, so the event vanished on those surfaces. Both render it now, through a shared `assignmentParticipantLabel` on the prompt side; the CLI keeps a three-line local copy rather than take a dependency on `@lobechat/prompts`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015wKZMihFpqsChKSb5YR23P * ⚡️ perf(task): show the assignment activity row in the same beat as the chip Picking a new assignee flipped the chip instantly (that dispatch was already optimistic) but the activity row explaining it only appeared after two serial round-trips: the mutation, then the full task-detail refetch — a fifteen-step aggregation that alone runs one to three seconds against a remote database. The chip said "done" while the feed stayed silent. The row is now synthesized in the same optimistic dispatch. The picker hands over the chosen item's display metadata, since it is the one place that already holds it — the store deliberately does not resolve members itself, that source being a business-layer hook. The actor is the signed-in user; with no known identity no row is invented. Only a slot that actually changes produces a row, mirroring the server's own rule, and the refetch that follows the mutation replaces the whole activity array, which retires the synthetic rows with no reconciliation. A failed mutation is rolled back by that same refetch. Comments share the identical two-round-trip lag and are left as they were. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015wKZMihFpqsChKSb5YR23P * ⚡️ perf(task): show a sent comment in the feed before the round-trip returns Comments had the same two-round-trip lag the assignment row just lost: the post, then the full task-detail refetch, before anything appeared. Sending now synthesizes the row in the same dispatch, authored as the signed-in user and carrying the topic it replies to, so it lands in the feed the moment the button is pressed. Attachments are deliberately left to the refetch — their metadata only exists once the server has resolved the file ids. A comment posted on an agent's behalf through the client-first runtime is also left to the refetch, since the store has no agent identity to attribute it to. While the synthesized row is on screen its edit / delete menu is withheld — the server has never seen that id, so either action would only surface an error. A failed post is rolled back by the same server-truth refetch the assignment path uses, and the failure still reaches the caller. The two builders now share one module and one id prefix, so a renderer has a single predicate for "not yet persisted". Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015wKZMihFpqsChKSb5YR23P * ✨ feat(task): log status, priority and automation changes made by a person The activity feed now explains every property a person can change from the task's property bar, not only the assignee. A status pick, a priority pick and turning automation on or off each leave a row: "<actor> changed the status to <tag>", "set the priority to <tag>", "set automation to <summary>". Only person-made changes are recorded — those made from the pickers or by an agent acting through its tool. The runner, lifecycle and watchdog transitions (backlog → running → completed) are deliberately not: the run row already carries them, and a duplicate line per run would be noise. The policy is not a flag; it is which writer a caller uses. Person-made edits go through the new `TaskModel.updateWithLog`, the system transitions keep the plain writers. `updateWithLog` replaces the per-property methods with one transactional diff over a tracked column set (assignees, status, priority and the four automation columns), so adding a tracked field is one list entry. The four automation columns fold into a single event carrying before / after snapshots: switching a schedule on rewrites mode, pattern and timezone in one save, and that is one decision, not three. All three task-detail formatters (page prompt, `viewTask` tool prompt, CLI) render the new event, through a shared `formatPropertyValue` on the prompt side. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015wKZMihFpqsChKSb5YR23P * ✨ feat(task): fold a burst of edits to one property into a single feed entry Fiddling with a property — priority High → Low → High inside a minute — produced one line per hop, where an issue tracker shows "moved from A to C" and shows nothing when the value ended up back where it started. Collapsing happens at read time, in the detail assembly, so the rows in `task_activities` stay append-only: the hop-by-hop history is still there for audit or a "show all" view, and every consumer of the detail (page, `viewTask` prompt, CLI) gets the same folded view. A run is one property by one person with no gap over the window; it keeps the newest row's time (when it settled), the first row's starting value and the last row's ending value. Two people never merge — A moving it and B moving it back are two decisions, not a cancelled one. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015wKZMihFpqsChKSb5YR23P * 💄 style(task): say where a property moved from, not only where it landed "changed the status to In Review" hides the half of the story a reader actually wants once a burst of edits has been folded into one entry: an entry can now span several hops, and the starting value is what says how far the property moved. Status, priority and automation rows read "from A to B"; automation keeps its one-sided forms for turning on ("set automation to") and off, since there is no other side to name. The two reworded keys had to be deleted from the generated locales and retranslated — the script only fills in missing keys. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015wKZMihFpqsChKSb5YR23P * 🐛 fix(task): surface every person-made property change in the feed at once Three findings from driving the real UI: **Automation changes never reached the feed.** `setAutomationMode` deliberately does no post-save refetch — an async SWR write could land after the user's next toggle and clobber it — so the row the server appended only showed up on the next unrelated refresh. The row now rides the same optimistic patch as the automation fields, inside the same engine transaction, so a failed save rolls it back with them. **Status and priority rows arrived a round-trip late.** Only assignment and comment rows were synthesized; the other two waited for the detail refetch, which against a remote database is the same lag the assignee row was fixed for. Both now ride the optimistic dispatch that flips their chip. **Priority read as "from ··· to !".** The priority tag alone is an icon; in a sentence it is noise. The level's name now rides along. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015wKZMihFpqsChKSb5YR23P * 🐛 fix(task): attribute client-runtime edits, tighten activity collapse and rollback - update/updateStatus accept an ownership-checked actorAgentId for the client-first runtime; the server-side actingAgentId still wins - collapse only extends a run when each hop starts where the last ended - optimistic property rows fold client-side; schedule edits get a row - addComment drops its synthesized row locally when the send fails - payload.actorKind keeps a deleted actor from reading as the system - task detail reads a bounded window of recent activity rows Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015wKZMihFpqsChKSb5YR23P * 🐛 fix(task): log cascades and rolled-back starts, quieter activity rows - collapse: any edit by someone else ends other actors' open runs - updateStatusCascade logs one status row per family member the confirming person cascaded; the router passes the actor - a person-made start rolled back after the heartbeat tick fails logs the undo too, so no orphan row survives the restore - activity rows read like an issue tracker's: 16px mark, plain names, values as words instead of chips, agent chip dropped Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015wKZMihFpqsChKSb5YR23P * 🐛 fix(task): lock cascade origins, batch their rows, widen the schedule value - cascade reads each task's leaving status under the row lock and writes the family's activity rows in one INSERT - the automation value includes the execution cap and its labels show timezone and cap, so a cap-only or timezone-only edit is visible - a failed status transition drops its synthesized row locally before the best-effort refetch Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015wKZMihFpqsChKSb5YR23P * 💄 style(task): lay the activity feed out as a timeline Compact rows lead with a type mark on a shared rail instead of the actor's avatar, read as one sentence in one tone, and sit tight together; cards still break the rail. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015wKZMihFpqsChKSb5YR23P * 💄 style(task): faces for people rows, property icons for property rows The rail mark is the actor's avatar on created / assignment lines and the property's icon (three-bar priority, status dot, timer) on change lines. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015wKZMihFpqsChKSb5YR23P * 💄 style(task): priority rows use the app's solid priority bars Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015wKZMihFpqsChKSb5YR23P * 💄 style(task): centre the activity rail under the cards' avatar column Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015wKZMihFpqsChKSb5YR23P --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a19bfe7d23 | ✨ feat(telegram): support Guest Mode summons without joining the chat (#18846) | ||
|
|
c04405343a |
✨ feat(goal): add nested experiment answers and exploration navigation (#19261)
* ✨ feat(goal): add nested experiment answers and exploration navigation * 📝 docs(goal): correct exploration budget command * 🐛 fix(cli): isolate dispatched agent run identity * 🐛 fix: register CLI notebook reports as operation deliverables * 🐛 fix: avoid signing notebook origins as file URLs * 🐛 fix(cli): retain bounded event backlog across temporary outages |
||
|
|
5f8b6a0556 |
🐛 fix: automatically review and retry Goal deliveries (#19257)
* 🐛 fix: automatically review and return Goal deliveries * ♻️ refactor: render Goal review feedback in prompts package * 🐛 fix: resolve configured models for Goal acceptance review * 🐛 fix: normalize provider config for Goal review model lookup |
||
|
|
a8b902e8da | 🐛 fix: preserve reviewed goal content and edit criteria in modals (#19247) | ||
|
|
b762bbdd7d | 🐛 fix(agent-runtime): show question in intervention summary (#19254) | ||
|
|
2f2e939df6 |
⚡️ perf: serve dashboard message totals from a planner estimate instead of a full COUNT (#19223)
* ⚡️ perf: serve dashboard message totals from a planner estimate instead of a full COUNT An exact COUNT over messages heap-fetches every row the user owns — tens of seconds for accounts with 100k+ messages — while the stats cards only need an order-of-magnitude total. MessageModel.countApproximate asks the planner for a row estimate first (EXPLAIN, a few ms); clearly heavy accounts get the estimate rounded to 3 significant digits, everyone else gets an exact count capped at 10k rows. message.count gains an opt-in approximate flag used by the dashboard totals; today's delta stays exact. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * 🐛 fix: address Codex review — log estimate failures, keep growth baseline exact The estimate path's catch no longer swallows errors silently: a driver or EXPLAIN regression would otherwise pin every past-cap account at cap + 1 with no signal. The stats card derives the last-month baseline as total minus an exact count of this month's messages instead of a second planner estimate — a user x date predicate has no multicolumn statistics, so estimating it directly can turn the displayed growth percentage into noise. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
18f6dba56e | 🐛 fix(opencode-go): preserve conversation session headers (#19235) | ||
|
|
5d5d97661d |
🐛 fix(task): keep recurring tasks free of delivery acceptance so a failed verify cannot disarm the schedule (#19220)
* 🐛 fix: keep recurring tasks free of delivery acceptance so a failed verify cannot disarm the schedule A recurring task (schedule / heartbeat) is a loop of ticks, not a delivery: instantiating a verify plan per tick meant a failed acceptance paused the task, and the cron query never picks paused tasks up again — the schedule was permanently disarmed. - instantiateVerifyPlanOnStart: skip automation tasks entirely (single choke point for all run-start callers) - driveTaskFromVerify: mirror the pass branch on failed/errored — record the verdict on the run, never pause a recurring task - buildTaskPrompt: drop the acceptance section from recurring-task prompts so the builder does not chase a plan that will never exist Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * 💄 style(task): hide the Verifier / acceptance section for recurring tasks The server no longer instantiates verify plans for automation tasks, so offering the config editor or an acceptance state row on a recurring task would advertise a contract that never runs. - TaskAcceptance: return null for automationMode tasks (single gate that covers the detail page, agent-scoped page, and both portals) and skip the acceptance-subject fetch - TaskProperties: drop the read-only acceptance state row Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ab22456df5 |
✨ feat(device): add remote directory browsing (#19221)
* ✨ feat(device): add remote directory browsing * 🔒️ fix(device): avoid logging remote paths |
||
|
|
5bf3a37e37 |
✨ feat(goal): north-star metrics strip on the goal detail header (#19206)
* ✨ feat(goal): north-star metrics strip on the goal detail header The goal header's six metrics are all execution metrics — status, tasks, findings, budget, duration, liveness — they say what the run cost, not how far the world is from the declared target. A goal like "grow to 10,000 followers" or "open security issues down to 0" had no surface tracking the number itself. The strip renders one card per measured acceptance clause (config.acceptance.metrics, #19133), joined with its metric series (#19063/#19071): current value vs target, progress normalized from the first observation as the baseline (a count-down clause reads 67% cleared, not a division by its zero target), sparkline trend, and observation freshness — data older than 48h renders as a warning, because a tracking surface is only as trustworthy as its data is fresh. A met clause turns the card green. The strip and the coordinator's measured gate read the same clauses, so they can never disagree about what is being tracked. Declaring and measuring close the loop from the UI: an empty goal shows a guidance row with a declare modal (key / condition / target → goal.setMetricCriteria), and each unmet card offers a record-observation modal (goal.recordObservation) — the server schedules an advance when the observation clears the gate, so recording the finishing number is enough to move the goal. Zero backend changes: clauses, series, points, observation writes and the gate all landed in #19063/#19071/#19133/#19151. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * 💄 style(goal): apply north-star review feedback from acceptance r1 Five items from the review round on acceptance 17679ed4: - Move the strip beside the requirement document (C3): the measured clauses are half of the acceptance contract, so they read with it — not squeezed between the title and the execution metrics. - Declare modal gains an optional display name (C2): the key stays the series address, the title is what tracking surfaces show. Carried on the clause (`GoalMetricCriterion.title`, accepted by both router schemas) and threaded into the first observation so the series inherits it. - Card background goes white (C4): `colorBgContainer` over the tinted fill. - Progress reads blue while climbing (C5): bar and sparkline use `colorInfo`; green stays reserved for the met state. - Met state loses the green slab (C7): no tinted background/border — the ✓ tag, value, bar and sparkline carry the success signal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * 🔥 chore: drop stray local goal-tracing artifact Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * 🐛 fix(goal): make the north-star strip scale-consistent, bounded, and honest Codex review on #19206 — two P1s and four P2s, all confirmed. Client/server verdict split (P1). The card compared raw values while the server's measured gate compares both operands at the persisted numeric(20, 6) scale, so a target like `eq 0.1234567` could keep a card unmet forever while the goal had already advanced. The card now rounds both operands with the same `toMetricScale` the gate uses. Poll amplification (P1). Every five-second refresh listed every series on the goal and then issued one listPoints RPC per series — 1+N requests and ~3N queries for series that cannot even affect the view. A new `metric.listSeriesWithPoints` returns the whole read in one RPC over K+2 bounded queries, restricted to the declared keys the caller names; the goal store passes exactly those. Windowed baseline (P2). Progress read its baseline from `points[0]` of a 200-point recent window, silently rebasing once history outgrew it (a 0→95/100 trajectory rendering as 50%). The endpoint carries each series' true first observation separately and the card reads its baseline from that. Met cards kept their record entry (P2): the world can regress, and a card whose only refresh path disappeared on success would stay falsely met forever — the ✓ tag and the + button now coexist. First fetch renders skeletons (P2) instead of confidently claiming "never measured" against an empty join before the request settles. Declaring merges server-side (P2): `setMetricCriteria` gains a `merge` mode that upserts by key against the list as it stands on the server, so two editors declaring from the same stale snapshot no longer erase each other; the declare modal uses it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ✅ test: cover the setMetricCriteria mode argument The router now forwards `mode` as a third service argument, so the exact-args assertion needed it; also locks the merge passthrough the declare path depends on. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f7103947cf |
🐛 fix(resource): stop agent artifacts from flooding user resource listings (#19215)
Users reported unfamiliar documents piling up in Resources — agent tool outputs (agent-document://) and skill artifacts were surfacing in the All tab and recent-pages sections because KnowledgeRepo's document arm only excluded sourceType 'file'. - KnowledgeRepo.documentArm now hides 'agent' / 'agent-signal' rows by default; knowledge-base listings opt out so explicitly filed agent docs stay visible - lobe-agent plans now carry sourceType 'agent' instead of 'api', with the workspace default-private visibility made explicit since the 'api' fallback in DocumentModel.create no longer applies - hoist the page source/file-type allowlists into @lobechat/const and drop the dead 'editor' value from SQL filters (client-only stamp) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4a935c75eb |
✨ feat(chat): inject the agent's name and title into the system context (#19214)
* ✨ feat(chat): inject the agent's name and title into the system context An agent's identity lives on the agent row (`name` / `title`), not in its prompt text — so an agent with a user-given name but no self-introduction in its systemRole answered "who are you?" with the product or model name instead of the name the user gave it. Add an `<agent_identity>` block (new AgentIdentityInjector, appended right after the system role) carrying the personal name and role title, wired through both pipelines: the client contextEngineering (from agent meta) and the server runtime context builder (from the agent config row). Group chat is excluded — GroupContextInjector already establishes identity there. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * 🐛 fix(agent): let the renamed default assistant introduce itself by its own name The inbox system role hardcoded "You are Lobe" — renaming the default assistant changed every surface except the model's own self-concept, so it kept answering "who are you?" as Lobe. Parametrize the inbox system role with the agent's name/title (RuntimeContext gains agentName/agentTitle), wired from the agent row at both runtime-config call sites (client agentConfigResolver + server resolveRunAgentConfig). Falls back to "Lobe" when the assistant was never renamed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8d852279db |
✨ feat(provider): add Unsloth provider support (#18339)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
31401e9ece | 🐛 fix: align goal task instructions with implementation outcomes (#19210) |