fix(office-ui): native progress parity, ui_state lock hardening, approval-card idempotency
Native agent progress panel (company mode): - ws_handler: filter runtime bookkeeping noise (turn/status/member_inbox_updated), keep tool_completed as tool_call, thinking summary previews content, preserve raw thinking_delta fragments (no strip; skip whitespace-only) - frontend progressLog: summarize thinking by content preview; merge thinking by detail only so the 'Thinking' label never splices into text - AgentProgressBlock: add bottom "Show more (N earlier steps)" toggle ui_state.db "database is locked" hardening: - ws_handler: isolate engine progress/kanban/runtime-event callbacks so UI persistence failures never crash work items - chat_store: busy_timeout, _retry_locked backoff, idempotent insert_message (INSERT OR REPLACE), create_channel read-before-write to stop poll writes - server: flock single-instance guard for `opc ui` per OPC home Approval card duplicate-click bug: - EscalationPanel: disable buttons on click with Submitting state and 30s reconnect fallback - ws_handler: stale-escalation branch checks real card status (new chat_store.get_checkpoint_message); already-resolved cards get an accurate "already handled (decision: X)" reply without being re-marked stale; dedup identical helper messages within 120s to stop reply spam Company mode prompt: - add soft guidance that the runtime monitors state and re-activates roles, so leaders need not poll work items after delegation/review Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -28,9 +28,41 @@ for (const [seq, detail] of [
|
||||
}
|
||||
|
||||
assert.equal(log.length, 1)
|
||||
assert.equal(log[0]?.summary, 'Thinking')
|
||||
assert.equal(log[0]?.summary, '我先联网抓取')
|
||||
assert.equal(log[0]?.detail, '我先联网抓取')
|
||||
|
||||
// Token-sized streaming fragments keep their whitespace when merged, and
|
||||
// entries without detail never splice their summary label into the text.
|
||||
let spacedLog = appendProgressEntry([], {
|
||||
timestamp: 100,
|
||||
type: 'thinking',
|
||||
summary: 'The user',
|
||||
detail: 'The user',
|
||||
turnId: 'rt-2:1',
|
||||
itemId: 'rt-2:1:thinking',
|
||||
seq: 1,
|
||||
})
|
||||
spacedLog = appendProgressEntry(spacedLog, {
|
||||
timestamp: 101,
|
||||
type: 'thinking',
|
||||
summary: 'wants to',
|
||||
detail: ' wants to',
|
||||
turnId: 'rt-2:1',
|
||||
itemId: 'rt-2:1:thinking',
|
||||
seq: 2,
|
||||
})
|
||||
spacedLog = appendProgressEntry(spacedLog, {
|
||||
timestamp: 102,
|
||||
type: 'thinking',
|
||||
summary: 'Thinking',
|
||||
turnId: 'rt-2:1',
|
||||
itemId: 'rt-2:1:thinking',
|
||||
seq: 3,
|
||||
})
|
||||
assert.equal(spacedLog.length, 1)
|
||||
assert.equal(spacedLog[0]?.detail, 'The user wants to')
|
||||
assert.equal(spacedLog[0]?.summary, 'The user wants to')
|
||||
|
||||
const unchanged = appendProgressEntry(log, {
|
||||
timestamp: 5,
|
||||
type: 'thinking',
|
||||
|
||||
@@ -27,9 +27,9 @@ function mergeText(left: string, right: string, kind: 'thinking' | 'tool_call'):
|
||||
}
|
||||
|
||||
function summarizeThinking(detail: string, fallback: string): string {
|
||||
void detail
|
||||
void fallback
|
||||
return 'Thinking'
|
||||
const text = detail.trim().replace(/\s+/g, ' ')
|
||||
if (!text) return fallback || 'Thinking'
|
||||
return text.length > 120 ? `${text.slice(0, 120).trimEnd()}...` : text
|
||||
}
|
||||
|
||||
function normalizeProgressEntry(entry: ProgressEntry): ProgressEntry {
|
||||
@@ -84,7 +84,10 @@ function isDuplicateProgress(left: ProgressEntry, right: ProgressEntry): boolean
|
||||
|
||||
function mergeProgress(left: ProgressEntry, right: ProgressEntry): ProgressEntry {
|
||||
if (left.type === 'thinking') {
|
||||
const detail = mergeText(left.detail ?? left.summary, right.detail ?? right.summary, 'thinking')
|
||||
// Merge detail text only: summary is a label/preview ("Thinking",
|
||||
// truncated excerpt), so falling back to it would splice label text
|
||||
// into the middle of the merged thinking stream.
|
||||
const detail = mergeText(left.detail ?? '', right.detail ?? '', 'thinking')
|
||||
return {
|
||||
timestamp: right.timestamp,
|
||||
type: 'thinking',
|
||||
|
||||
Reference in New Issue
Block a user