prts-terrarchive 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +84 -18
- package/README.md +64 -15
- package/THIRD_PARTY_NOTICES.md +10 -0
- package/bin/build-runtime.mjs +32 -0
- package/bin/check-runtime.mjs +56 -0
- package/bin/install.js +24 -5
- package/contracts/corpus_i18n_v1.schema.json +10 -10
- package/cordis.patch.yml +4 -4
- package/docs/web-access.md +24 -0
- package/lib/client.js +84 -23
- package/lib/rhine/rhine.css +1 -1
- package/lib/rhine/rhine.js +208 -208
- package/lib/runtime/licenses/js-yaml-MIT.txt +21 -0
- package/lib/runtime/licenses/zod-MIT.txt +21 -0
- package/lib/runtime/versions.json +4 -0
- package/lib/runtime/yaml.js +3062 -0
- package/lib/runtime/zod.js +14356 -0
- package/lib/skins/common.css +59 -0
- package/lib/skins/endfield-aic.css +22 -9
- package/lib/skins/prts-agent.css +15 -5
- package/lib/skins/rhine-lab.css +9 -0
- package/package.json +15 -8
- package/presets/composition.js +53 -0
- package/presets/definition.js +2 -2
- package/presets/prts/agent.cordis.yml +1 -1
- package/presets/prts/preset.yml +1 -1
- package/presets/register.js +23 -10
- package/resources/runtime/yaml.js +1 -0
- package/resources/runtime/zod.js +1 -0
- package/skills/prts-composition/SKILL.md +60 -0
- package/skills/prts-investigation/SKILL.md +19 -102
- package/skills/prts-investigation/references/board-guide.md +131 -0
- package/skills/prts-retrieval/SKILL.md +5 -3
- package/src/composition-skill.js +19 -0
- package/src/i18n.js +11 -17
- package/src/index.js +63 -81
- package/src/investigation-review.js +14 -6
- package/src/investigation-store.js +97 -25
- package/src/investigation-tools.js +67 -30
- package/src/investigation-validation.js +33 -0
- package/src/manual-search.js +1 -1
- package/src/skill.js +25 -16
- package/src/skin-capabilities.js +28 -0
- package/src/tools.js +13 -0
- package/src/web-guidance.js +44 -0
- package/ui/rhine/archive-rack.ts +10 -8
- package/ui/rhine/catalogue.ts +16 -4
- package/ui/rhine/evidence-board-panel.ts +15 -0
- package/ui/rhine/investigation-board.css +3 -0
- package/ui/rhine/investigation-board.ts +71 -24
- package/ui/rhine/investigation-history.ts +15 -0
- package/ui/rhine/reading-room.css +14 -3
- package/ui/rhine/scene.ts +44 -24
- package/ui/rhine/tool-activity.ts +13 -6
- package/ui/rhine/types.ts +6 -0
- package/ui/rhine/workbench.ts +82 -40
|
@@ -9,6 +9,7 @@ import { boardPlaneTransform } from './board-plane-transform';
|
|
|
9
9
|
import { INVESTIGATION_BOARD_PLANE } from './investigation-board-plane';
|
|
10
10
|
import { InvestigationEditRevisions } from './investigation-edits';
|
|
11
11
|
import { referenceSource } from './source-reading';
|
|
12
|
+
import { investigationVersion } from './investigation-history';
|
|
12
13
|
import './investigation-board.css';
|
|
13
14
|
|
|
14
15
|
type Panel = { closeTools():void; getCards(): EvidenceCard[]; remapIds(mapping:Record<string,string>,revisions?:Record<string,{content_revision:number;layout_revision:number}>):void; replaceCards(cards: EvidenceCard[], reset?: boolean): void; select(id: string | null, edit?: boolean): void };
|
|
@@ -18,9 +19,13 @@ export interface InvestigationContext {
|
|
|
18
19
|
selectedId: string;
|
|
19
20
|
workingId: string;
|
|
20
21
|
ready: boolean;
|
|
22
|
+
version: number;
|
|
23
|
+
readOnly: boolean;
|
|
24
|
+
reportAvailable: boolean;
|
|
25
|
+
report?: { version:number; title:string; clues:number; sources:number };
|
|
21
26
|
}
|
|
22
27
|
type Options = { onContext?(value: InvestigationContext): void; onRackRevision?(revision:number):void; sessionId: string; api(endpoint: string, payload?: any, signal?: AbortSignal): Promise<any>;
|
|
23
|
-
panel: Panel; onInbox(value:EvidenceInboxView,open:boolean):void; askInbox?(boardId:string,title:string):Promise<void>; onReading(value: ReadingObject | null): void; onCards(cards: EvidenceCard[]): void; openSource(source: ArchiveSource, inbox?:boolean): void; notify(text: string): void };
|
|
28
|
+
panel: Panel; onInbox(value:EvidenceInboxView,open:boolean):void; askInbox?(boardId:string,title:string):Promise<void>; onReading(value: ReadingObject | null): void; onCards(cards: EvidenceCard[], total: number): void; openSource(source: ArchiveSource, inbox?:boolean): void; notify(text: string): void };
|
|
24
29
|
const REPORT_ID = 'investigation-report';
|
|
25
30
|
const labels: Record<string, string> = { excerpt: '原文摘录', finding: '研究发现', time: '时间节点', relation: '关键关联', question: '未解问题', contrast: '交叉对照' };
|
|
26
31
|
const uid = () => crypto.randomUUID();
|
|
@@ -35,6 +40,8 @@ export function mountInvestigationBoard(host: HTMLElement, options: Options) {
|
|
|
35
40
|
let sessionId = options.sessionId, board: any = null, catalog: any[] = [], sourceRows: any[] = [];
|
|
36
41
|
let selectedId = '', workingId = '', followWorking = true, page = 0, commitSeq = 0, disposed = false, active = false;
|
|
37
42
|
let contextReady = false;
|
|
43
|
+
let liveBoard:any=null, liveSources:any[]=[];
|
|
44
|
+
let selectedVersion=0, historyComplete=true;
|
|
38
45
|
let syncing = false, saving = 0, refreshPending = false, loadEpoch = 0, loading = false;
|
|
39
46
|
let baseline: EvidenceCard[] = [], sceneCards: EvidenceCard[] = [];
|
|
40
47
|
let edits = new InvestigationEditRevisions(), pendingReset = false;
|
|
@@ -56,11 +63,12 @@ export function mountInvestigationBoard(host: HTMLElement, options: Options) {
|
|
|
56
63
|
const index = el('span','rhine-investigation-index');
|
|
57
64
|
index.append(el('b','','RHINE LAB'),el('small','','RESEARCH / 研究调查'));
|
|
58
65
|
const picker = el('select','rhine-investigation-picker'); picker.setAttribute('aria-label','切换调查板');
|
|
66
|
+
const history = el('select','rhine-investigation-version'); history.setAttribute('aria-label','证据板版本');
|
|
59
67
|
const status = el('span','rhine-investigation-status'); status.setAttribute('role','status');
|
|
60
68
|
const follow = el('button','rhine-investigation-follow','查看工作板 ↗'); follow.type='button'; follow.hidden=true;
|
|
61
69
|
const directory = el('button','rhine-investigation-directory','线索目录 ↗'); directory.type='button';
|
|
62
70
|
const add = el('button','rhine-investigation-add','+ 手动建板'); add.type='button';
|
|
63
|
-
header.append(index,picker,status,follow,directory,add); hud.append(header);
|
|
71
|
+
header.append(index,picker,history,status,follow,directory,add); hud.append(header);
|
|
64
72
|
const footer=el('div','rhine-investigation-footer');
|
|
65
73
|
const previous=el('button','','←'), next=el('button','','→'), paging=el('span','','');
|
|
66
74
|
previous.type=next.type='button'; previous.setAttribute('aria-label','上一组线索'); next.setAttribute('aria-label','下一组线索');
|
|
@@ -158,26 +166,50 @@ export function mountInvestigationBoard(host: HTMLElement, options: Options) {
|
|
|
158
166
|
const latest=board?.reports.at(-1), running=catalog.find(b=>b.id===selectedId)?.run?.status==='running';
|
|
159
167
|
sceneCards=board?[{id:REPORT_ID,title:latest?.title||(running?'调查进行中':'等待调查报告'),body:latest?.summary||'重要线索正在汇入,报告将在调查完成后发布。',kind:'note',stage:2,clueKind:'report',
|
|
160
168
|
evidenceLabel:latest?`REPORT / V${String(latest.version).padStart(2,'0')}`:'RESEARCH / IN PROGRESS',sourceLabel:`${activeClues(board).length} 条线索 · ${latest?.sources?.length || 0} 份来源`,position:{x:0,y:0},rotation:0,scale:1},...baseline]:[];
|
|
161
|
-
options.onCards(sceneCards);
|
|
169
|
+
options.onCards(sceneCards, activeClues(board).length);
|
|
162
170
|
}
|
|
163
171
|
function getContext(): InvestigationContext {
|
|
164
|
-
|
|
172
|
+
const report=board?.reports.at(-1);
|
|
173
|
+
return { sessionId, boards: catalog.map(({id,title})=>({id,title})), selectedId, workingId, ready: contextReady, version:selectedVersion, readOnly:!!selectedVersion, reportAvailable:!!report, ...(report?{report:{version:report.version,title:report.title,clues:report.clues.length,sources:report.sources.length}}:{}) };
|
|
165
174
|
}
|
|
166
175
|
function holdReadingContext() {
|
|
167
176
|
// A source detour must return to the same board even if the agent starts another investigation.
|
|
168
177
|
followWorking=false;
|
|
169
|
-
|
|
178
|
+
savePreference();
|
|
179
|
+
}
|
|
180
|
+
function savePreference(version=selectedVersion) {
|
|
181
|
+
try { if(selectedId)localStorage.setItem(preference(),JSON.stringify({id:selectedId,version})); } catch {}
|
|
182
|
+
}
|
|
183
|
+
function projectVersion() {
|
|
184
|
+
const view=investigationVersion(liveBoard,liveSources,selectedVersion);
|
|
185
|
+
board=view.board;sourceRows=view.sources;historyComplete=view.complete;
|
|
186
|
+
}
|
|
187
|
+
async function selectVersion(version:number, reading=false) {
|
|
188
|
+
const generation=aborter;
|
|
189
|
+
options.panel.closeTools();
|
|
190
|
+
await queue;
|
|
191
|
+
if(disposed||generation!==aborter)return;
|
|
192
|
+
if(version&&!liveBoard?.reports.some((r:any)=>r.version===version))return;
|
|
193
|
+
cancelReturn();drawer.hidden=true;report.hidden=true;inbox.hide(true);
|
|
194
|
+
selectedVersion=version;followWorking=false;page=0;savePreference();
|
|
195
|
+
projectVersion();edits=new InvestigationEditRevisions();renderCards(true);render();syncReading();
|
|
196
|
+
if(reading)showReport(version||undefined);
|
|
170
197
|
}
|
|
171
198
|
function render() {
|
|
172
199
|
options.onContext?.(getContext());
|
|
173
|
-
inbox.
|
|
200
|
+
// The inbox is editable live state; historical views expose saved clues only.
|
|
201
|
+
inbox.setData(selectedVersion?null:board,selectedVersion?[]:sourceRows);
|
|
174
202
|
const value=selectedId;picker.replaceChildren();
|
|
175
203
|
for(const row of catalog){const option=el('option','',`${row.title} · ${row.clueCount} 条线索`);option.value=row.id;picker.append(option);}picker.value=value;
|
|
176
204
|
picker.disabled=!catalog.length||!!saving||loading;picker.hidden=!catalog.length;empty.hidden=!!board;directory.disabled=!board;
|
|
177
205
|
follow.disabled=add.disabled=!!saving||loading;
|
|
178
|
-
follow.hidden=!workingId||workingId===selectedId;
|
|
206
|
+
follow.hidden=!selectedVersion&&(!workingId||workingId===selectedId);
|
|
207
|
+
history.replaceChildren();const current=el('option','','工作板');current.value='0';history.append(current);
|
|
208
|
+
for(const r of [...(liveBoard?.reports||[])].reverse()){const option=el('option','',`V${r.version}${r.boardSnapshot?'':' · 报告依据'}`);option.value=String(r.version);history.append(option);}
|
|
209
|
+
history.value=String(selectedVersion);history.hidden=!liveBoard?.reports.length;history.disabled=!!saving||loading;
|
|
210
|
+
hint.textContent=selectedVersion?historyComplete?'历史快照 · 只读 · 点击阅读':'旧版本仅保留报告依据,连线与完整布局未保存':'重要线索随调查加入 · 点击阅读,双击编辑';
|
|
179
211
|
const row=catalog.find(b=>b.id===selectedId),latest=board?.reports.at(-1);
|
|
180
|
-
status.textContent=saving?'正在保存…':loading?'正在同步…':!board?'尚无调查':row?.run?.status==='running'?'● Agent 正在整理':row?.run?.status==='interrupted'?'已暂停 · 线索已保留':latest?`报告 V${latest.version}${row?.reportStale?' · 有新线索待更新':''}`:'线索已保存';
|
|
212
|
+
status.textContent=selectedVersion?`V${selectedVersion} · ${historyComplete?'整板快照':'部分依据'} · 只读`:saving?'正在保存…':loading?'正在同步…':!board?'尚无调查':row?.run?.status==='running'?'● Agent 正在整理':row?.run?.status==='interrupted'?'已暂停 · 线索已保留':latest?`报告 V${latest.version}${row?.reportStale?' · 有新线索待更新':''}`:'线索已保存';
|
|
181
213
|
const pages=pageCount(board);previous.disabled=page===0||!!saving||loading;next.disabled=page>=pages-1||!!saving||loading;
|
|
182
214
|
paging.textContent=`${page+1} / ${pages} · ${activeClues(board).length} 条线索`;footer.hidden=!board;
|
|
183
215
|
}
|
|
@@ -201,7 +233,10 @@ export function mountInvestigationBoard(host: HTMLElement, options: Options) {
|
|
|
201
233
|
cancelReturn();page=0;drawer.hidden=true;report.hidden=true;edits=new InvestigationEditRevisions();editTarget={boardId:selectedId};
|
|
202
234
|
}
|
|
203
235
|
if(saving){refreshPending=true;loading=false;return;}
|
|
204
|
-
|
|
236
|
+
if(oldId&&oldId!==selectedId)selectedVersion=0;
|
|
237
|
+
liveBoard=result?.board||null;liveSources=result?.sources||[];
|
|
238
|
+
if(selectedVersion&&!liveBoard?.reports.some((r:any)=>r.version===selectedVersion))selectedVersion=0;
|
|
239
|
+
projectVersion();
|
|
205
240
|
page=Math.min(page,pageCount(board)-1);loading=false;contextReady=true;
|
|
206
241
|
const resetCards=pendingReset||oldId!==selectedId;pendingReset=false;renderCards(resetCards);render();
|
|
207
242
|
}
|
|
@@ -218,7 +253,7 @@ export function mountInvestigationBoard(host: HTMLElement, options: Options) {
|
|
|
218
253
|
drawerLabel.textContent=`${clue.id} / ${labels[clue.kind]}`;drawerBody.replaceChildren();
|
|
219
254
|
drawerBody.append(el('h2','',clue.title),el('p','rhine-investigation-provenance',`${clue.interpretation==='inference'?'推断':clue.interpretation==='question'?'待核验':'观察'} · ${clue.editedBy==='user'?'用户整理':'Agent 整理'}`));
|
|
220
255
|
const content=el('div','rhine-investigation-clue-text rhine-markdown');renderReportMarkdown(content,clue.detail||clue.summary);drawerBody.append(content);
|
|
221
|
-
const edit=el('button','','编辑这条线索 ↗');edit.type='button';edit.onclick=()=>{drawer.hidden=true;options.panel.select(id,true);};drawerBody.append(edit);
|
|
256
|
+
const edit=el('button','','编辑这条线索 ↗');edit.type='button';edit.hidden=!!selectedVersion;edit.onclick=()=>{drawer.hidden=true;options.panel.select(id,true);};drawerBody.append(edit);
|
|
222
257
|
drawerBody.append(el('h3','','来源与核验'));
|
|
223
258
|
for(const ref of clue.sources){const source=sourceFor(ref);if(!source)continue;const button=el('button','rhine-investigation-source',`${source.agentRead?'已读':'检索摘要'} · ${source.title} ↗`);button.type='button';button.onclick=()=>options.openSource(source);drawerBody.append(button);if(ref.quote)drawerBody.append(el('blockquote','',ref.quote));}
|
|
224
259
|
if(!clue.sources.length)drawerBody.append(el('p','','尚无来源,不能作为已核实结论。'));
|
|
@@ -245,9 +280,9 @@ export function mountInvestigationBoard(host: HTMLElement, options: Options) {
|
|
|
245
280
|
function showReport(version?:number) {
|
|
246
281
|
cancelReturn();inbox.hide(true);
|
|
247
282
|
if(!board?.reports.length){options.notify('报告尚未发布,已保存的线索可以先阅读。');return;}
|
|
248
|
-
options.panel.closeTools();
|
|
249
|
-
versions.replaceChildren();for(const r of [...board.reports].reverse()){const o=el('option','',`V${r.version} · ${new Date(r.publishedAt).toLocaleString()}`);o.value=String(r.version);versions.append(o);}
|
|
250
|
-
const r=board.reports.find((r:any)=>r.version===version)||board.reports.at(-1);versions.value=String(r.version);reportTitle.textContent=r.title;
|
|
283
|
+
holdReadingContext();options.panel.closeTools();
|
|
284
|
+
versions.replaceChildren();for(const r of [...(liveBoard?.reports||board.reports)].reverse()){const o=el('option','',`V${r.version} · ${new Date(r.publishedAt).toLocaleString()}`);o.value=String(r.version);versions.append(o);}
|
|
285
|
+
const r=(liveBoard?.reports||board.reports).find((r:any)=>r.version===(version||selectedVersion))||board.reports.at(-1);versions.value=String(r.version);reportTitle.textContent=r.title;
|
|
251
286
|
reportNotice.textContent=`${r.clues.length} 条依据 · ${r.sources.length} 份来源 · ${r.basisKnowledgeRevision!==board.knowledgeRevision?'发布后已有线索更新,以下保留当时版本':'已保存版本'}`;
|
|
252
287
|
renderReportMarkdown(reportArticle,r.markdown.replace(/\[(C\d+)\](?!\()/g, (_match:string,id:string) => r.clues.some((c:any)=>c.id===id) ? `[${id}](#investigation-clue-${id})` : `[${id}]`));
|
|
253
288
|
const details=el('details','rhine-investigation-report-evidence');details.append(el('summary','','查看此版本使用的线索与来源'));
|
|
@@ -257,6 +292,7 @@ export function mountInvestigationBoard(host: HTMLElement, options: Options) {
|
|
|
257
292
|
}
|
|
258
293
|
function userChange(rawCards:EvidenceCard[]) {
|
|
259
294
|
if(syncing||disposed)return;
|
|
295
|
+
if(selectedVersion){renderCards(true);options.notify('历史板只读;返回工作板后可继续编辑。');return;}
|
|
260
296
|
const cards=structuredClone(rawCards), before=structuredClone(baseline), sources=sourceRows;
|
|
261
297
|
const ledger=edits, context=editTarget, target=board?.id||context.boardId, currentSession=sessionId, generation=aborter;
|
|
262
298
|
const current=()=>!disposed&&generation===aborter;
|
|
@@ -287,9 +323,9 @@ export function mountInvestigationBoard(host: HTMLElement, options: Options) {
|
|
|
287
323
|
return {changes,relations};
|
|
288
324
|
}
|
|
289
325
|
const {changes,relations}=prepare();
|
|
290
|
-
if(!changes.length&&!relations.length){options.onCards([...sceneCards.filter(c=>c.id===REPORT_ID),...cards]);return;}
|
|
326
|
+
if(!changes.length&&!relations.length){options.onCards([...sceneCards.filter(c=>c.id===REPORT_ID),...cards],activeClues(board).length-toCards().length+cards.length);return;}
|
|
291
327
|
baseline=cards;saving++;render();
|
|
292
|
-
options.onCards([...sceneCards.filter(c=>c.id===REPORT_ID),...cards]);
|
|
328
|
+
options.onCards([...sceneCards.filter(c=>c.id===REPORT_ID),...cards],activeClues(board).length-toCards().length+cards.length);
|
|
293
329
|
const send=(endpoint:string,payload:any)=>options.api(`investigation.${endpoint}`,{session_id:currentSession,...payload},generation.signal);
|
|
294
330
|
queue=queue.then(async()=>{
|
|
295
331
|
if(!current())throw new DOMException('编辑会话已切换','AbortError');
|
|
@@ -312,20 +348,31 @@ export function mountInvestigationBoard(host: HTMLElement, options: Options) {
|
|
|
312
348
|
}
|
|
313
349
|
async function start() {
|
|
314
350
|
const generation=aborter;
|
|
315
|
-
try{const preferenceValue=localStorage.getItem(preference());
|
|
351
|
+
try{const preferenceValue=localStorage.getItem(preference());
|
|
352
|
+
if(preferenceValue?.startsWith('{')){const saved=JSON.parse(preferenceValue);selectedId=typeof saved.id==='string'?saved.id:'';selectedVersion=Number.isSafeInteger(saved.version)&&saved.version>0?saved.version:0;}
|
|
353
|
+
else selectedId=preferenceValue||'';followWorking=!selectedId;
|
|
354
|
+
}catch{}
|
|
316
355
|
try{await migrate();if(generation!==aborter||disposed)return;await refresh(true);if(generation===aborter&&!disposed)void loop(generation);}catch(error){if(generation===aborter&&!disposed){fail(error);void loop(generation);}}
|
|
317
356
|
}
|
|
318
|
-
picker.onchange=()=>{selectedId=picker.value;followWorking=false;page=0;
|
|
319
|
-
|
|
357
|
+
picker.onchange=()=>{selectedId=picker.value;followWorking=false;page=0;savePreference(0);void refresh(true).catch(fail);};
|
|
358
|
+
history.onchange=()=>{void selectVersion(Number(history.value)).catch(fail);};
|
|
359
|
+
follow.onclick=()=>{void (async()=>{
|
|
360
|
+
const generation=aborter;
|
|
361
|
+
// Replace the historical projection before making the working board editable.
|
|
362
|
+
await selectVersion(0);
|
|
363
|
+
if(disposed||generation!==aborter)return;
|
|
364
|
+
followWorking=true;page=0;try{localStorage.removeItem(preference());}catch{}
|
|
365
|
+
await refresh(true);
|
|
366
|
+
})().catch(fail);};
|
|
320
367
|
previous.onclick=()=>{page=Math.max(0,page-1);renderCards(true);render();};next.onclick=()=>{page++;renderCards(true);render();};
|
|
321
|
-
directory.onclick=showDirectory;close.onclick=closeReading;reportClose.onclick=closeReading;versions.onchange=()=>
|
|
368
|
+
directory.onclick=showDirectory;close.onclick=closeReading;reportClose.onclick=closeReading;versions.onchange=()=>{void selectVersion(Number(versions.value),true).catch(fail);};
|
|
322
369
|
add.onclick=()=>{cancelReturn();options.panel.closeTools();report.hidden=true;drawerLabel.textContent='NEW / 新建手动调查';drawerBody.replaceChildren();const input=el('input','');input.placeholder='调查标题';input.maxLength=120;input.setAttribute('aria-label','新调查标题');const button=el('button','','创建调查板');button.type='button';button.onclick=async()=>{if(!input.value.trim())return;button.disabled=true;try{const r=await request('create',{mutation_id:uid(),title:input.value.trim()});selectedId=r.board_id;followWorking=false;drawer.hidden=true;await refresh(true);}catch(error){fail(error);button.disabled=false;}};drawerBody.append(input,button);showReading(drawer);input.focus();};
|
|
323
370
|
void start();
|
|
324
|
-
return { userChange, handleKeydown, getContext, holdReadingContext, getCards:()=>sceneCards, select(id:string|null,edit=false){cancelReturn();drawer.hidden=true;report.hidden=true;if(id===REPORT_ID){showReport();return;}if(id
|
|
371
|
+
return { showReport, async prepareReport(){if(loading||!contextReady)await refresh();return !!board?.reports.length;}, userChange, handleKeydown, getContext, holdReadingContext, getCards:()=>sceneCards, select(id:string|null,edit=false){cancelReturn();drawer.hidden=true;report.hidden=true;if(id===REPORT_ID){showReport();return;}if(id&&(!edit||selectedVersion))showClue(id);else options.panel.select(id,edit);},
|
|
325
372
|
resumeReading(kind:'report'|'clue'|'inbox'){if(!active)return;if(kind==='inbox'){inbox.open();return;}cancelReturn();if(kind==='report'){showReading(report);report.focus({preventScroll:true});}else showReading(drawer);syncReading();},
|
|
326
|
-
setBoardFrame, setInboxAnchor:(value:EvidenceInboxAnchor)=>inbox.setAnchor(value), getInboxState:()=>inbox.state(), openInbox(){options.panel.closeTools();cancelReturn();report.hidden=true;drawer.hidden=true;syncReading();inbox.open();},
|
|
373
|
+
setBoardFrame, setInboxAnchor:(value:EvidenceInboxAnchor)=>inbox.setAnchor(value), getInboxState:()=>inbox.state(), openInbox(){if(selectedVersion){options.notify('历史板只读;返回工作板后查看重点证据盒。');return;}options.panel.closeTools();cancelReturn();report.hidden=true;drawer.hidden=true;syncReading();inbox.open();},
|
|
327
374
|
setActive(value:boolean){active=value;inbox.setActive(value);hud.hidden=!value||!planeVisible;if(!value){cancelReturn();drawer.hidden=true;report.hidden=true;}syncReading();},
|
|
328
|
-
async setSession(id:string){if(id===sessionId)return;cancelReturn();aborter.abort();aborter=new AbortController();loadEpoch++;sessionId=id;board=null;selectedId='';workingId='';contextReady=false;catalog=[];sourceRows=[];baseline=[];edits=new InvestigationEditRevisions();editTarget={boardId:''};queue=Promise.resolve();saving=0;refreshPending=false;pendingReset=false;commitSeq=0;page=0;drawer.hidden=true;report.hidden=true;renderCards(true);render();await start();},
|
|
375
|
+
async setSession(id:string){if(id===sessionId)return;cancelReturn();aborter.abort();aborter=new AbortController();loadEpoch++;sessionId=id;board=null;liveBoard=null;liveSources=[];selectedVersion=0;selectedId='';workingId='';contextReady=false;catalog=[];sourceRows=[];baseline=[];edits=new InvestigationEditRevisions();editTarget={boardId:''};queue=Promise.resolve();saving=0;refreshPending=false;pendingReset=false;commitSeq=0;page=0;drawer.hidden=true;report.hidden=true;renderCards(true);render();await start();},
|
|
329
376
|
stageSource(source:ArchiveSource,targetId?:string){
|
|
330
377
|
const currentSession=sessionId,generation=aborter,targetAtClick=targetId ?? (workingId||selectedId);
|
|
331
378
|
const send=(endpoint:string,payload:Record<string,unknown>)=>options.api(`investigation.${endpoint}`,{session_id:currentSession,...payload},generation.signal);
|
|
@@ -341,8 +388,8 @@ export function mountInvestigationBoard(host: HTMLElement, options: Options) {
|
|
|
341
388
|
});
|
|
342
389
|
inboxStageQueue=task.catch(()=>{});return task;
|
|
343
390
|
},
|
|
344
|
-
async addSource(source:ArchiveSource){if(loading)await refresh();let target=selectedId;if(!target){const created=await request('create',{mutation_id:uid()});target=created.board_id;selectedId=target;followWorking=false;}await request('edit',{mutation_id:uid(),board_id:target,sources:[source],changes:[{title:source.title.slice(0,120),summary:source.excerpt.slice(0,480),detail:source.excerpt.slice(0,12000),kind:'excerpt',interpretation:'question',sources:[{source_id:source.id}]}]});await refresh();},
|
|
391
|
+
async addSource(source:ArchiveSource){if(selectedVersion)throw new Error('历史板只读;返回工作板后再添加线索。');if(loading)await refresh();let target=selectedId;if(!target){const created=await request('create',{mutation_id:uid()});target=created.board_id;selectedId=target;followWorking=false;}await request('edit',{mutation_id:uid(),board_id:target,sources:[source],changes:[{title:source.title.slice(0,120),summary:source.excerpt.slice(0,480),detail:source.excerpt.slice(0,12000),kind:'excerpt',interpretation:'question',sources:[{source_id:source.id}]}]});await refresh();},
|
|
345
392
|
dispose(){disposed=true;inbox.dispose();cancelReturn();for(const transition of readingTransitions.values())transition.dispose();visibility.disconnect();options.onReading(null);aborter.abort();events.abort();hud.remove();drawer.remove();report.remove();},
|
|
346
|
-
stats(){return{inbox:inbox.stats(),selectedId,workingId,boards:catalog.length,clues:activeClues(board).length,reportVersions:board?.reports.length||0,reportOpen:!report.hidden,returning:returnStartedAt!==undefined,drawerOpen:!drawer.hidden,commitSeq,active,saving};},
|
|
393
|
+
stats(){return{inbox:inbox.stats(),selectedVersion,historyComplete,selectedId,workingId,boards:catalog.length,clues:activeClues(board).length,reportVersions:board?.reports.length||0,reportOpen:!report.hidden,returning:returnStartedAt!==undefined,drawerOpen:!drawer.hidden,commitSeq,active,saving};},
|
|
347
394
|
};
|
|
348
395
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Historical boards never borrow mutable clues or relations from the working board. */
|
|
2
|
+
export function investigationVersion(board: any, sources: any[], version: number) {
|
|
3
|
+
if (!board || !version) return { board, sources, complete: true };
|
|
4
|
+
const report = board.reports.find((item: any) => item.version === version);
|
|
5
|
+
if (!report) throw new Error('该调查版本不存在,请重新选择版本。');
|
|
6
|
+
const saved = report.boardSnapshot;
|
|
7
|
+
const historical = saved || {
|
|
8
|
+
id: board.id, title: report.title, objective: report.summary,
|
|
9
|
+
createdAt: report.publishedAt, updatedAt: report.publishedAt,
|
|
10
|
+
knowledgeRevision: report.basisKnowledgeRevision, layoutRevision: 0,
|
|
11
|
+
clues: report.clues, relations: [], openQuestions: [], evidenceInbox: [], inboxRevision: 0,
|
|
12
|
+
};
|
|
13
|
+
return { board: structuredClone({ ...historical, reports: [report] }),
|
|
14
|
+
sources: structuredClone(saved?.sources || report.sources), complete: Boolean(saved) };
|
|
15
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* A physical document at left,
|
|
1
|
+
/* A physical document at left, an opaque paper reading surface at right. */
|
|
2
2
|
.rhine-workbench{--reading-ink:#30362b;--reading-muted:#7d806e;--reading-rule:#aca78f88}
|
|
3
3
|
.rhine-workbench :is(.rhine-investigation-report,.rhine-investigation-drawer){position:absolute;inset:0;z-index:45;width:auto;max-width:none;padding:0;border:0;box-shadow:none;backdrop-filter:none;overflow:hidden;color:var(--reading-ink);background:linear-gradient(90deg,transparent 38%,#ebe8e04d 50%,#ebe8e0e0 72%,#ebe8e0eb 100%)}
|
|
4
4
|
.rhine-workbench .rhine-investigation-report>header,.rhine-workbench .rhine-investigation-drawer-top{position:absolute;left:56%;right:5%;top:22%;height:36px;display:flex;align-items:center;justify-content:space-between;gap:14px;padding:0;border:0;font-size:11px;letter-spacing:.06em;color:var(--reading-muted)}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
.rhine-workbench .rhine-investigation-report .rhine-report-body>:not(.rhine-report-table){max-width:none}
|
|
15
15
|
.rhine-workbench .rhine-investigation-provenance{padding-bottom:20px;border-bottom:1px solid var(--reading-rule);color:var(--reading-muted)}
|
|
16
16
|
.rhine-workbench .rhine-report.modal-backdrop{background:transparent;backdrop-filter:none}
|
|
17
|
-
.rhine-workbench .rhine-report-modal{position:absolute;left:56%;right:5%;top:22%;bottom:12%;width:auto;height:auto;max-height:none;padding:0;background
|
|
17
|
+
.rhine-workbench .rhine-report-modal{position:absolute;left:56%;right:5%;top:22%;bottom:12%;width:auto;height:auto;max-height:none;padding:0;background:#f4f1e8;box-shadow:0 12px 50px #393a2520;border:1px solid #c6c2ae;padding:24px;box-sizing:border-box}
|
|
18
18
|
.rhine-workbench .rhine-report-layout{grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr) auto;gap:16px;padding-top:16px}
|
|
19
19
|
.rhine-workbench .rhine-report-outline{max-height:68px;overflow:auto;border:0}
|
|
20
20
|
.rhine-workbench .rhine-report-toc{display:flex;gap:16px;flex-wrap:wrap}
|
|
@@ -46,10 +46,21 @@
|
|
|
46
46
|
.rhine-workbench :is(.rhine-investigation-report,.rhine-investigation-drawer){animation:none}
|
|
47
47
|
|
|
48
48
|
/* Board controls stay in place behind the reading mode, then return unchanged. */
|
|
49
|
-
.rhine-workbench.has-reading-object :is(.rhine-investigation-hud,.rhine-board-toolbar,.rhine-board-chalk-hit,.rhine-evidence-panel,.rhine-zone-nav,.rhine-scene-edges,.rhine-board-tool-status){visibility:hidden;pointer-events:none}
|
|
49
|
+
.rhine-workbench.has-reading-object :is(.rhine-investigation-hud,.rhine-board-toolbar,.rhine-board-chalk-hit,.rhine-evidence-panel,.rhine-zone-nav,.rhine-scene-edges,.rhine-board-tool-status,.rhine-investigation-brief,.rhine-agent-form,.rhine-rack-legend,.rhine-shelf-report){visibility:hidden;pointer-events:none}
|
|
50
50
|
.rhine-workbench .rhine-investigation-report .rhine-report-body{box-sizing:border-box;margin:0;justify-self:stretch}
|
|
51
51
|
.rhine-workbench .rhine-investigation-report .rhine-report-body>:not(.rhine-report-table){margin-left:0;margin-right:0;max-width:none}
|
|
52
52
|
|
|
53
53
|
/* Keep the fading layer as the pointer shield until the fade completes. */
|
|
54
54
|
.rhine-workbench :is(.rhine-investigation-report,.rhine-investigation-drawer)[data-transition=closing]{animation:none;pointer-events:auto}
|
|
55
55
|
.rhine-workbench :is(.rhine-investigation-report,.rhine-investigation-drawer)[data-transition=closing]>*{pointer-events:none}
|
|
56
|
+
|
|
57
|
+
/* Saved reports keep their book on the left and a continuous paper surface on the right. */
|
|
58
|
+
.rhine-workbench .rhine-investigation-report::before,.rhine-workbench .rhine-investigation-drawer::before{content:"";position:absolute;left:52%;right:3%;top:15%;bottom:9%;background:#f4f1e8;border:1px solid #c6c2ae;box-shadow:0 12px 50px #393a2520;pointer-events:none}
|
|
59
|
+
.rhine-workbench .rhine-investigation-report>header,.rhine-workbench .rhine-investigation-drawer-top{left:54%;right:5%;top:18%;border-bottom:1px solid var(--reading-rule);padding-bottom:12px}
|
|
60
|
+
.rhine-workbench .rhine-investigation-report-scroll,.rhine-workbench .rhine-investigation-drawer-body{left:54%;top:26%;bottom:12%;padding-right:14px}
|
|
61
|
+
.rhine-workbench .rhine-investigation-report-intro>h2{font-size:clamp(21px,1.8vw,28px);line-height:1.5;overflow-wrap:anywhere}
|
|
62
|
+
.rhine-workbench .rhine-investigation-report .rhine-report-body{font-size:16px;line-height:1.9}
|
|
63
|
+
.rhine-workbench .rhine-report-title{font-size:25px;line-height:1.4;margin:12px 0}
|
|
64
|
+
.rhine-workbench :is([data-layout=compact],[data-layout=portrait]) :is(.rhine-investigation-report,.rhine-investigation-drawer)::before{left:49%;right:2%;top:13%;bottom:6%}
|
|
65
|
+
.rhine-workbench [data-layout=portrait] :is(.rhine-investigation-report,.rhine-investigation-drawer)::before{left:3%;right:3%;top:37%;bottom:3%}
|
|
66
|
+
.rhine-workbench [data-layout=portrait] .rhine-report-modal{padding:18px}
|
package/ui/rhine/scene.ts
CHANGED
|
@@ -123,11 +123,12 @@ export async function createRhineScene(host: HTMLElement, options: RhineSceneOpt
|
|
|
123
123
|
let userPriorityUntil = 0;
|
|
124
124
|
let followAgent = true;
|
|
125
125
|
const userOwnsView = () => !followAgent || performance.now() < userPriorityUntil;
|
|
126
|
-
const prioritizeUser = () => { userPriorityUntil = performance.now() + 4000;
|
|
126
|
+
const prioritizeUser = () => { userPriorityUntil = performance.now() + 4000; };
|
|
127
127
|
let scanElapsed = ARCHIVE_STEP_SECONDS - 0.35;
|
|
128
128
|
let scanSteps = 0;
|
|
129
129
|
let readingScanDirection = 1;
|
|
130
130
|
let arrivalScanRemaining = 0;
|
|
131
|
+
let searchVisualRemaining = 0;
|
|
131
132
|
let deskAmount = 0;
|
|
132
133
|
let boardAmount = 0;
|
|
133
134
|
let boardZoom = BOARD_ZOOM_MIN;
|
|
@@ -261,10 +262,11 @@ export async function createRhineScene(host: HTMLElement, options: RhineSceneOpt
|
|
|
261
262
|
const position = Math.max(0, lanes[lane].findIndex(item => sourceIdentity(item) === sourceIdentity(source)));
|
|
262
263
|
return lane * 8 + wrap(slotRows[lane] + position - laneRows[lane], 8);
|
|
263
264
|
};
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
265
|
+
const activityCancelled = () => investigation?.outcome === 'interrupted' || investigation?.outcome === 'error';
|
|
266
|
+
const readingActive = () => !activityCancelled() && ([...operations.values()].some(item => item.kind === 'read' && item.state === 'active')
|
|
267
|
+
|| (!!investigation?.running && investigation.phase === 'reading'));
|
|
268
|
+
const scanningActive = () => searching || !activityCancelled() && ([...operations.values()].some(item => item.kind === 'search' && item.state === 'active')
|
|
269
|
+
|| (!!investigation?.searching && investigation.phase !== 'reading') || arrivalScanRemaining > 0 || searchVisualRemaining > 0);
|
|
268
270
|
const archiveBusy = () => readingActive() || scanningActive();
|
|
269
271
|
|
|
270
272
|
// Preserve the authored shell; resting files can share a captured interior.
|
|
@@ -467,36 +469,36 @@ export async function createRhineScene(host: HTMLElement, options: RhineSceneOpt
|
|
|
467
469
|
}, target.axis === 'pointer' ? 5 : target.axis === 'activity' ? 4 : -2);
|
|
468
470
|
}
|
|
469
471
|
};
|
|
470
|
-
const notifyArchive = () => {
|
|
472
|
+
const notifyArchive = (automatic = false) => {
|
|
471
473
|
const source = currentArchiveSource();
|
|
472
474
|
const inspected = location === 'desk' && selectedId ? sourceList.find(item => item.id === selectedId) : undefined;
|
|
473
475
|
const labelSource = inspected || source;
|
|
474
476
|
original.setArchiveLabel(labelSource ? archivePrint(labelSource) : null);
|
|
475
477
|
syncLabelPrefetch();
|
|
476
|
-
options.onArchiveSelect?.(archiveIndex);
|
|
478
|
+
if (!automatic || !userOwnsView()) options.onArchiveSelect?.(archiveIndex);
|
|
477
479
|
// Empty lanes still participate in the original mechanical loop. Keep
|
|
478
480
|
// the last real UI selection, otherwise source synchronization repeatedly
|
|
479
481
|
// falls back to the first result and pulls the moving track backwards.
|
|
480
|
-
if (source || !archiveBusy()) options.onArchiveSourceSelect?.(source?.id || null, archiveLaneIndex, userOwnsView());
|
|
482
|
+
if ((!automatic || !userOwnsView()) && (source || !archiveBusy())) options.onArchiveSourceSelect?.(source?.id || null, archiveLaneIndex, userOwnsView());
|
|
481
483
|
};
|
|
482
|
-
const chooseArchive = (index: number, navigation?: ArchiveNavigation) => {
|
|
484
|
+
const chooseArchive = (index: number, navigation?: ArchiveNavigation, automatic = false) => {
|
|
483
485
|
archiveIndex = wrap(index, 40);
|
|
484
486
|
archiveLaneIndex = fileLocation(archiveIndex).lane;
|
|
485
487
|
slotRows[archiveLaneIndex] = archiveIndex % 8;
|
|
486
488
|
if (detail) { detail = false; original.setMode('archive'); }
|
|
487
489
|
original.select(archiveIndex, navigation);
|
|
488
|
-
notifyArchive(); wake();
|
|
490
|
+
notifyArchive(automatic); wake();
|
|
489
491
|
};
|
|
490
|
-
const stepArchive = (axis: 'row' | 'lane', direction: number) => {
|
|
492
|
+
const stepArchive = (axis: 'row' | 'lane', direction: number, automatic = false) => {
|
|
491
493
|
if (disposed || location !== 'archive' || detail) return;
|
|
492
494
|
const step = direction < 0 ? -1 : 1;
|
|
493
495
|
if (axis === 'row') {
|
|
494
496
|
const count = lanes[archiveLaneIndex].length;
|
|
495
497
|
if (count) laneRows[archiveLaneIndex] = wrap(laneRows[archiveLaneIndex] + step, count);
|
|
496
|
-
chooseArchive(archiveLaneIndex * 8 + wrap(slotRows[archiveLaneIndex] + step, 8), { axis: 'row', direction: step });
|
|
498
|
+
chooseArchive(archiveLaneIndex * 8 + wrap(slotRows[archiveLaneIndex] + step, 8), { axis: 'row', direction: step }, automatic);
|
|
497
499
|
} else {
|
|
498
500
|
const next = wrap(archiveLaneIndex + step, archiveColumns.length);
|
|
499
|
-
chooseArchive(next * 8 + slotRows[next], { axis: 'lane', direction: step });
|
|
501
|
+
chooseArchive(next * 8 + slotRows[next], { axis: 'lane', direction: step }, automatic);
|
|
500
502
|
}
|
|
501
503
|
};
|
|
502
504
|
const acceptArchiveNavigation = (intent: Record<string, string | number> = {}) => {
|
|
@@ -711,7 +713,14 @@ export async function createRhineScene(host: HTMLElement, options: RhineSceneOpt
|
|
|
711
713
|
|| previous === undefined && (Number.isFinite(operation.completedAt)
|
|
712
714
|
? operation.completedAt! < activitySince : snapshot.loadingHistory));
|
|
713
715
|
if (historical) historicalOperations.add(operation.id);
|
|
714
|
-
|
|
716
|
+
// A quick or repeated search must still finish a visible mechanical step.
|
|
717
|
+
// Refreshes of the same receipt never replay it; old chat history is a baseline.
|
|
718
|
+
if (!historical && !historicalOperations.has(operation.id) && operation.kind === 'search'
|
|
719
|
+
&& operation.state !== 'error' && previous !== operation.state && active && !document.hidden && !reduced && !activityCancelled()) {
|
|
720
|
+
searchVisualRemaining = ARCHIVE_STEP_SECONDS + 0.4;
|
|
721
|
+
if (previous === undefined) scanElapsed = Math.max(scanElapsed, ARCHIVE_STEP_SECONDS - 0.2);
|
|
722
|
+
}
|
|
723
|
+
if (operation.kind !== 'read' || operation.state === 'error' || historical || activityCancelled()
|
|
715
724
|
|| previous === 'complete' && !scheduledReads.has(`${operation.id}:0`)) continue;
|
|
716
725
|
const returned = operationSources(operation);
|
|
717
726
|
const targets: (ArchiveSource | undefined)[] = returned.length ? returned : [undefined];
|
|
@@ -747,7 +756,7 @@ export async function createRhineScene(host: HTMLElement, options: RhineSceneOpt
|
|
|
747
756
|
clearActivities(); transferred.clear(); waitingArrivals.clear();
|
|
748
757
|
observedOperations.clear(); historicalOperations.clear(); scheduledReads.clear(); operations.clear(); operationsInitialized = false;
|
|
749
758
|
activitySince = Date.now();
|
|
750
|
-
userPriorityUntil = 0; searching = false; arrivalScanRemaining = 0;
|
|
759
|
+
userPriorityUntil = 0; searching = false; arrivalScanRemaining = 0; searchVisualRemaining = 0;
|
|
751
760
|
scanElapsed = ARCHIVE_STEP_SECONDS - 0.35; scanSteps = 0;
|
|
752
761
|
selectedId = null; focusedId = null; shelfPage = 0;
|
|
753
762
|
setSources([], false); setArchiveSources([]);
|
|
@@ -755,7 +764,13 @@ export async function createRhineScene(host: HTMLElement, options: RhineSceneOpt
|
|
|
755
764
|
sessionId = snapshot.sessionId;
|
|
756
765
|
// Local directory loading and host investigation state have independent
|
|
757
766
|
// lifetimes. Streaming snapshots must not reset the mechanical scan timer.
|
|
758
|
-
observeOperations();
|
|
767
|
+
observeOperations();
|
|
768
|
+
if (activityCancelled()) {
|
|
769
|
+
searchVisualRemaining = arrivalScanRemaining = 0;
|
|
770
|
+
activityQueue.length = 0;
|
|
771
|
+
for (const actor of activityFiles) beginReturn(actor);
|
|
772
|
+
}
|
|
773
|
+
wake();
|
|
759
774
|
};
|
|
760
775
|
|
|
761
776
|
const occupyingArray = (actor: ActivityFile) => actor.stage !== 'travelling' || actor.elapsed < 2.4 * 0.28;
|
|
@@ -807,9 +822,9 @@ export async function createRhineScene(host: HTMLElement, options: RhineSceneOpt
|
|
|
807
822
|
if (operation?.state === 'error' || request.age > 9 && operation?.state !== 'active') activityQueue.splice(index, 1);
|
|
808
823
|
}
|
|
809
824
|
if (activityQueue.length !== queuedBefore) { activityLabelSource = null; syncLabelPrefetch(); }
|
|
810
|
-
//
|
|
811
|
-
//
|
|
812
|
-
if (!reduced && !detail && !cameraTravel
|
|
825
|
+
// Manual browsing owns the camera and selected document, not Agent activity.
|
|
826
|
+
// A short read still receives its complete minimum physical sequence.
|
|
827
|
+
if (!reduced && !detail && !cameraTravel
|
|
813
828
|
&& activityFiles.length < MAX_ACTIVITY_FILES && activityQueue.length && activitySpacing === 0) {
|
|
814
829
|
const requestSlot = (request: ActivityRequest) => request.source ? sourceSlot(request.source)
|
|
815
830
|
: Array.from({ length: MAX_ACTIVITY_FILES + 1 }, (_, offset) => archiveLaneIndex * 8 + wrap(slotRows[archiveLaneIndex] + offset, 8))
|
|
@@ -834,7 +849,7 @@ export async function createRhineScene(host: HTMLElement, options: RhineSceneOpt
|
|
|
834
849
|
}
|
|
835
850
|
if (next >= 0) {
|
|
836
851
|
const request = activityQueue.splice(next, 1)[0];
|
|
837
|
-
const focusSource = location === 'archive' && !activityFiles.some(occupyingArray) && request.source;
|
|
852
|
+
const focusSource = !userOwnsView() && location === 'archive' && !activityFiles.some(occupyingArray) && request.source;
|
|
838
853
|
if (focusSource) focusArchiveSource(focusSource.id);
|
|
839
854
|
const index = requestSlot(request);
|
|
840
855
|
const number = request.source ? sourceNumber(request.source) : -1;
|
|
@@ -1052,7 +1067,7 @@ export async function createRhineScene(host: HTMLElement, options: RhineSceneOpt
|
|
|
1052
1067
|
try {
|
|
1053
1068
|
const dt = lastFrame ? Math.min(0.05, (ms - lastFrame) / 1000) : 0.016;
|
|
1054
1069
|
lastFrame = ms;
|
|
1055
|
-
const scanning = scanningActive() && location === 'archive' && !detail && !reduced
|
|
1070
|
+
const scanning = scanningActive() && location === 'archive' && !detail && !reduced;
|
|
1056
1071
|
original.setInvestigationScanning(scanning);
|
|
1057
1072
|
// Use precisely the original select path: track, shoulder, signed pulse and
|
|
1058
1073
|
// preview lift. Allow its spring to settle before advancing to the next cell.
|
|
@@ -1065,15 +1080,16 @@ export async function createRhineScene(host: HTMLElement, options: RhineSceneOpt
|
|
|
1065
1080
|
if (activityFiles.some(item => item.stage === 'holding')) {
|
|
1066
1081
|
// A parallel search can keep moving beside a long read without
|
|
1067
1082
|
// carrying its pinned cassette all the way out of the composition.
|
|
1068
|
-
stepArchive('row', readingScanDirection);
|
|
1083
|
+
stepArchive('row', readingScanDirection, true);
|
|
1069
1084
|
readingScanDirection *= -1;
|
|
1070
1085
|
} else {
|
|
1071
1086
|
readingScanDirection = 1;
|
|
1072
|
-
stepArchive(++scanSteps % 3 === 0 ? 'lane' : 'row', 1);
|
|
1087
|
+
stepArchive(++scanSteps % 3 === 0 ? 'lane' : 'row', 1, true);
|
|
1073
1088
|
}
|
|
1074
1089
|
}
|
|
1075
1090
|
}
|
|
1076
1091
|
arrivalScanRemaining = Math.max(0, arrivalScanRemaining - dt);
|
|
1092
|
+
searchVisualRemaining = Math.max(0, searchVisualRemaining - dt);
|
|
1077
1093
|
updateCollection(dt);
|
|
1078
1094
|
evidenceBoard.update(dt);
|
|
1079
1095
|
options.performanceProbe?.checkpoint(measured, 'collection');
|
|
@@ -1350,13 +1366,17 @@ export async function createRhineScene(host: HTMLElement, options: RhineSceneOpt
|
|
|
1350
1366
|
pointRay(event);
|
|
1351
1367
|
if(!boardPanKey&&!middlePan&&boardTool.mode==='select'&&evidenceInbox.pick(raycaster)){down=null;event.preventDefault();options.onInboxOpen?.();return;}
|
|
1352
1368
|
let id = evidenceBoard.pick(raycaster);
|
|
1369
|
+
if (options.isBoardReadOnly?.() && !boardPanKey && !middlePan) {
|
|
1370
|
+
if (id) { down = null; event.preventDefault(); options.onBoardSelect?.(id, false); return; }
|
|
1371
|
+
if (evidenceBoard.pickTool(raycaster)) { down = null; return; }
|
|
1372
|
+
}
|
|
1353
1373
|
if (!boardPanKey && !middlePan && (evidenceBoard.pickTool(raycaster) || boardTool.mode !== 'select')) {
|
|
1354
1374
|
boardPress = { pointerId: event.pointerId, kind: evidenceBoard.pickTool(raycaster) ? 'chalk' : 'tool', cardId: id, moved: false };
|
|
1355
1375
|
event.preventDefault(); original.renderer.domElement.setPointerCapture(event.pointerId);
|
|
1356
1376
|
updateToolPreview(event); return;
|
|
1357
1377
|
}
|
|
1358
1378
|
if (!boardPanKey && !middlePan && boardTool.mode === 'select') {
|
|
1359
|
-
let resizeId = pickResizeHandle(event);
|
|
1379
|
+
let resizeId = options.isBoardReadOnly?.() ? null : pickResizeHandle(event);
|
|
1360
1380
|
if (resizeId) {
|
|
1361
1381
|
// Flush/promote an edited sample before freezing the resize reference.
|
|
1362
1382
|
options.onBoardSelect?.(resizeId, false);
|
|
@@ -47,12 +47,19 @@ export function operationSource(operation: ArchiveOperation, sources: ArchiveSou
|
|
|
47
47
|
|
|
48
48
|
/** One card per real read call, including a repeat read of the same document. */
|
|
49
49
|
export function latestReadFocus(snapshot: InvestigationSnapshot, sources: ArchiveSource[]) {
|
|
50
|
-
const reads = (snapshot.operations || []).filter(call => call.kind === 'read');
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
const reads = (snapshot.operations || []).filter(call => call.kind === 'read').slice().reverse();
|
|
51
|
+
if (snapshot.lastRead) reads.push(snapshot.lastRead);
|
|
52
|
+
let failed: { key: string; source: ArchiveSource; operation: ArchiveOperation } | undefined;
|
|
53
|
+
for (const operation of reads) {
|
|
54
|
+
const source = operationSource(operation, sources);
|
|
55
|
+
if (!source) continue;
|
|
56
|
+
const focus = { key: `${snapshot.sessionId}:${operation.id}`, source, operation };
|
|
57
|
+
// A failed request must not replace the last actual reading target. Keep
|
|
58
|
+
// its error card only when there is no earlier read to return to.
|
|
59
|
+
if (operation.state !== 'error') return focus;
|
|
60
|
+
failed ??= focus;
|
|
61
|
+
}
|
|
62
|
+
return failed;
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
/** New calls move the whole card downward once; streamed snapshots stay still. */
|
package/ui/rhine/types.ts
CHANGED
|
@@ -26,6 +26,8 @@ export interface ArchiveSource {
|
|
|
26
26
|
origin: ArchiveOrigin;
|
|
27
27
|
state: ArchiveState;
|
|
28
28
|
agentRead?: boolean;
|
|
29
|
+
/** Delivered to the Agent in this session; independent of reading or citation. */
|
|
30
|
+
agentReceived?: boolean;
|
|
29
31
|
excerpt: string;
|
|
30
32
|
/** The exact web_fetch text received by the Agent, separate from the preview excerpt. */
|
|
31
33
|
content?: string;
|
|
@@ -100,6 +102,8 @@ export interface InvestigationSnapshot {
|
|
|
100
102
|
records?: { id: string; tool: string; state: 'active' | 'complete' | 'error'; query: string; text: string }[];
|
|
101
103
|
/** Current-turn tool activities; parallel and already completed calls remain distinct. */
|
|
102
104
|
operations?: ArchiveOperation[];
|
|
105
|
+
/** Latest successful read in loaded session history, retained across turns. */
|
|
106
|
+
lastRead?: ArchiveOperation;
|
|
103
107
|
/** All current-turn Host tools, including nested and non-corpus calls. */
|
|
104
108
|
toolCalls?: InvestigationToolCall[];
|
|
105
109
|
}
|
|
@@ -141,6 +145,7 @@ export interface RhineScene {
|
|
|
141
145
|
dispose(): void;
|
|
142
146
|
}
|
|
143
147
|
export interface RhineSceneOptions {
|
|
148
|
+
isBoardReadOnly?: () => boolean;
|
|
144
149
|
assetBase: string;
|
|
145
150
|
navigationLimiter?: import('./archive-navigation-limit').ArchiveNavigationLimiter;
|
|
146
151
|
/** TEMPORARY RHINE PROFILER */
|
|
@@ -183,6 +188,7 @@ export interface RhineOptions {
|
|
|
183
188
|
isSettingsOpen?: () => boolean;
|
|
184
189
|
}
|
|
185
190
|
export interface RhineHostState {
|
|
191
|
+
cancelling?: boolean;
|
|
186
192
|
sessionId: string;
|
|
187
193
|
title: string;
|
|
188
194
|
workspace: string;
|