Repository navigation
Commit fccde75
fix(plugin-chatbot,app-shell): the follow-up sent to the model after Confirm or Cancel is no longer shown as the person's message (#30296)
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} -->
_Requested by **Yibo Wang** · [project
thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwB3raJW9Z4ixrGxfuEz9hefp)_
Fixes #30223
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:聊天确认卡片点「确认」或「取消」后,系统发给模型的那段续写指令(`[HITL pa_…] The operator
approved …`)不再显示成用户自己的气泡,对话列表预览也不再显示它;模型照常收到这段文字。
- 为什么改:客户在自己那一侧看到一大段带 id 和 JSON 的英文,像泄露或故障;重新打开对话、左侧对话列表里也一直显示它。
- 风险与回滚:只改界面读取,存储和发给模型的内容一字不变;识别靠续写文字开头的 `[HITL 编号]`
标记,用户自己恰好以这种格式开头打字才会被误隐藏。回滚即还原本 PR。
-
先看:`ui/packages/plugin-chatbot/src/hitlContinuation.ts`(标记的唯一出处)、`ChatbotEnhanced.tsx`
中 `isHitlContinuation` 那一行、`useConversationList.ts` 的 `extractPreview`。
- 两分钟验证:`cd ui && pnpm exec vitest run
packages/plugin-chatbot/src/__tests__/hitlContinuation-30223.test.tsx`,预期
5 例全绿;截图见下方 Verification。
- 需要你做的:无,席位落地。
## What changes
- **One marker, one module.** New
`ui/packages/plugin-chatbot/src/hitlContinuation.ts` owns the
continuation tag: `hitlContinuationTag(id)` writes `[HITL PA_ID]`,
`isHitlContinuation(text)` reads it (tag at the very start, one
non-space id, then a space). `useHitlInChat` now builds its prompt with
`hitlContinuationTag`, so producer and reader cannot drift. The text
sent to the model is byte-identical to before.
- **Transcript.** `ChatbotEnhanced` draws no bubble for a user message
whose content is a continuation. That one seam covers the live chat, a
reloaded conversation, the dock (it mounts the same `ChatPane`) and the
shared-transcript page (it renders `ChatbotEnhanced` read-only).
- **Conversation list preview.** `useConversationList`'s
`extractPreview` reads past continuations to the person's last real ask;
a server-supplied `preview` that is a continuation is dropped too. The
list's search haystack is built from the same `preview`, so search stops
matching the continuation as well.
- **Conversation-language probe.** `detectConversationLanguage`
(app-shell) skipped nothing: after a confirm, the latest "user" turn was
the English continuation, so a Chinese conversation's outbound text
(plan approve, change-confirm send text) turned English whenever the
action's result carried no Chinese. It now reads past the continuation.
Same marker, same defect class (the continuation read as the person's
turn), one line.
- **Export.** `isHitlContinuation` is exported from
`@object-ui/plugin-chatbot` and `@object-ui/plugin-chatbot/core` (the
app-shell readers import it from `./core`, which stays side-effect
free). The producer `hitlContinuationTag` stays internal.
## Why
The ruling asked to prefer a marker carried on the message (metadata or
a part flag) over the `[HITL …]` prefix, and to stop if the clean fix
needs a server change. Measured: **nothing but the text survives
storage.**
- The agent route collapses an inbound UI message's `parts` to their
joined text (`normalizeMessage` in `@objectstack/service-ai`), and
`addMessage` persists `content` as that string.
- `ai_messages` has the columns `id`, `conversation_id`, `role`,
`content`, `tool_calls`, `tool_call_id`, `turn_id` and token/latency
columns; no column holds message metadata. `GET /conversations/:id`
rebuilds each user message as `{ role: 'user', content: row.content }`.
- The share endpoint's raw rows (`RawAiMessageRow` in
`useChatConversation.ts`) carry the same columns.
Read from the last published `@objectstack/service-ai` (10.3.0, the npm
tarball); the current service-ai is Cloud/EE and not in this repository,
so its source was not read. The staging evidence on the card (the
continuation text in the stored history and the list preview) agrees. A
UI `metadata` flag would therefore exist only until the first reload,
and two markers would be two dialects. The leading tag is the one marker
that round-trips, so it is the marker, and its spelling now lives in one
module next to its reader. No server, REST or spec change.
Seam for a structural marker later, if wanted: `cloud:service-ai
normalizeMessage + ai_messages (no metadata column) →
renderer:ChatbotEnhanced, app-shell:useConversationList`.
## Tests
New pins (all on the real hooks and renderer, no component mocks):
- `plugin-chatbot/src/__tests__/hitlContinuation-30223.test.tsx` —
`useHitlInChat` decides over a mocked pending-actions endpoint and hands
its continuation to the REAL `useObjectChat` transport. 确认 leg and 取消
leg: the captured chat POST's last user message is the full
continuation, tag first (the model-facing payload); the hook holds it as
a user message; `ChatbotEnhanced` (through `toRuntimeMessages`, as the
console page does) draws the person's ask and no continuation. CONTROL:
an ordinary typed message is sent verbatim and drawn. Reader unit: the
producer's tag reads true; ordinary text, a tag not at the start,
`[HITL]` with no id and non-strings read false.
- `app-shell/src/console/ai/__tests__/hitlContinuation-30223.test.tsx` —
reload leg over stored rows through the real hydration path
(`aiMessageRowsToServerMessages` → `toUIMessages` →
`hydratedMessagesToChatMessages` → `ChatbotEnhanced`): the continuation
hydrates as a user message and draws no bubble; CONTROL: an ordinary
stored user message still draws. Language probe: a Chinese ask followed
by an English continuation stays `zh-CN`; CONTROL: an English turn the
person typed still decides.
-
`app-shell/src/hooks/__tests__/useConversationList.hitlContinuation-30223.test.tsx`
— the confirmed conversation previews the ask, not the continuation
stored after it; CONTROL: a conversation without a confirm previews its
last user message unchanged; a conversation holding only a continuation
(and a served `preview` equal to it) gets no preview rather than the
continuation.
<details><summary>Ablation (one per reader; mutation landed, restored,
blob == HEAD)</summary>
Run through `scripts/ablation-replace.mjs` on commit `6244d3a5`; each
mutation reported `anchor 1 -> 0` and each restore `blob == HEAD`, `git
diff HEAD` empty. The ui suites import `@object-ui/*` from source, so no
rebuild between legs.
- A1 `ChatbotEnhanced.tsx`: `if (isUser &&
isHitlContinuation(message.content)) return null;` → `… && false …`.
Red: 确认 leg, 取消 leg, reload leg (3 failed / 6 passed; every CONTROL
stayed green).
- A2 `useConversationList.ts`: `if (text && !isHitlContinuation(text))
return text;` → `if (text) return text;`. Red: `Expected "把这条状态改成
Active"`, `Received "[HITL pa_8cf40802-… The operator approved the
update_record call…"`.
- A3 `conversationLanguage.ts`: the `continue` on a continuation
disabled. Red: "a Chinese conversation stays Chinese after an English
continuation" (1 failed / 3 passed).
</details>
<details><summary>Suites and type-check</summary>
On `6244d3a5` under `scripts/pm/os-verify-lock.sh`, from `ui/`:
- `vitest run packages/plugin-chatbot/` — Test Files 95 passed (95),
Tests 880 passed (880).
- `vitest run` over the 140 app-shell test files that import
`useConversationList`, `conversationLanguage`, `AiChatPage`,
`ConversationsSidebar` or `ChatDock` — Test Files 140 passed (140),
Tests 992 passed (992). Narrowed: the rest of app-shell does not import
a touched module; CI's Test UI runs it all.
- `pnpm --filter @object-ui/app-shell type-check` exit 0. `pnpm --filter
@object-ui/plugin-chatbot type-check` first exited 2 on the new test
(hook output handed to `ChatbotEnhanced` without `toRuntimeMessages`);
fixed in `f572a55d`, then exit 0.
After merging `origin/main` (`632f7d95`): the three new files plus
`useHitlInChat.test.tsx` — Test Files 4 passed (4), Tests 19 passed
(19).
</details>
## Acceptance notes
- **Card Acceptance.** (1) After 确认 or 取消 no continuation bubble: hidden
(the one-line note option was not taken; it would need a new label in
all ten locale packs). (2) The model still receives the continuation:
pinned on the captured chat POST. (3) A reloaded conversation does not
show it, including the conversation list (comment 6648541899): pinned on
the hydration path and on `useConversationList`.
- **Recognition is by the leading tag.** A person who types a message
that itself starts with `[HITL ` + an id + `] ` would not see that
bubble. The tag is not something the product ever asks a person to type.
- **Out of scope, reported not fixed** (the card's two smaller things):
the assistant reply sometimes names the record by its id (that is model
wording over the result JSON, which carries `recordId` and a `link` with
the label); the record page behind the dock does not refresh after a
chat write. Neither is touched here.
- **File surface beyond the claim's list.** Besides `useHitlInChat.ts`,
the transcript renderer and the list-preview reader, this diff adds
`plugin-chatbot/src/hitlContinuation.ts`, two export lines (`core.ts`,
`index.tsx`) and the language-probe line in
`app-shell/src/console/ai/conversationLanguage.ts` (another reader of
the same stored turn, same defect class).
## Verification
Live leg NOT run: the in-product agent runtime
(`@objectstack/service-ai`) is Cloud/EE and absent from the open
framework (`/ai/**` answers 501 there), and no LLM provider is
configured in this container. Closest deterministic fixture instead:
- The mock-transport legs above (real `useHitlInChat` + real
`useObjectChat` transport + real renderer).
- A real-Chromium render of the console's own modules (a scratch
dev-server page, not committed): `ConversationsSidebar` over a stubbed
`GET /conversations`, beside `ChatbotEnhanced`, both fed a transcript
with one confirmed update and one cancelled create. Before (both readers
swapped back to `origin/main`, served code checked): the cancel
continuation shows as a user bubble and as the list preview. After:
neither; the list previews the last real ask 「再新建一个客户 验收客户 R6」. Text
probe of the page: before `chatHasHitl: true, listHasHitl: true`; after
`false, false`. Shots:
`/mnt/project-files/ui-orders/30223-shots/before-fix.png`,
`after-fix.png` (project files).
<details><summary>Derived gates: 35 run, 35 exit 0 (HEAD
632f7d9)</summary>
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` on `632f7d95` (after merging `origin/main`,
merge base `959c26be1`) derived 35 commands; each was run with its exit
code captured before any pipe, all 35 exit 0. `--ran` reconciliation:
"35 derived famil(ies) accounted for — 35 run, 0 NOT-MEASURED". The
roster family the derivation flagged under `.changeset` was run as well:
`pnpm check:changeset-fixed` exit 0. Type-check lanes, artifact rosters
and wide-population families are CI's (the PR check-runs).
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(not touched)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes. (measured locally as `npx
eslint` over the touched files from each package: 0 errors; the new
`index.tsx` export line adds one `react-refresh/only-export-components`
warning, the barrel's existing pattern. Repo-wide `pnpm lint` is CI's
run.)
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed. (not touched)
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English. (the maintainer brief
is in Chinese by repo rule)
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01WGufzfQigsiRb7grXXevft
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 977be38 commit fccde75
11 files changed
Lines changed: 480 additions & 4 deletions
File tree
- .changeset
- ui/packages
- app-shell/src
- console/ai
- __tests__
- hooks
- __tests__
- plugin-chatbot/src
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| 15 | + | |
| 16 | + | |
15 | 17 | | |
16 | 18 | | |
17 | 19 | | |
| |||
49 | 51 | | |
50 | 52 | | |
51 | 53 | | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
52 | 59 | | |
53 | 60 | | |
54 | 61 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
13 | 14 | | |
14 | 15 | | |
15 | 16 | | |
| |||
76 | 77 | | |
77 | 78 | | |
78 | 79 | | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
79 | 83 | | |
80 | 84 | | |
81 | 85 | | |
82 | 86 | | |
83 | | - | |
| 87 | + | |
84 | 88 | | |
85 | 89 | | |
86 | 90 | | |
87 | | - | |
| 91 | + | |
| 92 | + | |
88 | 93 | | |
89 | 94 | | |
90 | 95 | | |
| |||
122 | 127 | | |
123 | 128 | | |
124 | 129 | | |
125 | | - | |
| 130 | + | |
| 131 | + | |
126 | 132 | | |
127 | 133 | | |
128 | 134 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
| 51 | + | |
51 | 52 | | |
52 | 53 | | |
53 | 54 | | |
| |||
4533 | 4534 | | |
4534 | 4535 | | |
4535 | 4536 | | |
| 4537 | + | |
| 4538 | + | |
| 4539 | + | |
| 4540 | + | |
| 4541 | + | |
4536 | 4542 | | |
4537 | 4543 | | |
4538 | 4544 | | |
| |||
0 commit comments