Extend external context screenshot readiness surface

This commit is contained in:
Codex
2026-05-15 17:19:19 +08:00
parent 35accb7884
commit 4bfdd271bf
8 changed files with 329 additions and 13 deletions
+5 -1
View File
@@ -716,12 +716,14 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
- Latest E11 validation passed: 16 focused external-context/static tests, 730 full unit tests, 73 script static checks, `node --check src/web/app.js`, live deploy completed, remote ops smoke passed 9 checks, and remote E11 smoke confirmed capture surface summary/cards, external visual evidence chain entry, and deployed `Open Capture Surface` / `Open Capture Target` / `Open Capture Embed` UI strings on the live host.
- Latest W8 validation passed: 69 focused workflow bridge/API/static tests, 731 full unit tests, 73 script static checks, `node --check src/web/app.js`, live deploy completed, remote ops smoke passed 9 checks, and remote W8 smoke confirmed handoff packet starter, replay note bridge summary, artifact bundle starter export, and deployed `Prefill Compare Replay Note` / `Export Handoff Starter` / `Export Artifact Bundle` UI strings on the live host.
- Latest O2 validation passed: 74 focused archive/API/static/operator tests, 732 full unit tests, 73 script static checks, `node --check src/web/app.js`, live deploy completed, remote ops smoke passed 9 checks, and remote O2 smoke confirmed persisted digest/workflow baselines, long-range trend stories, and deployed `Save Baseline` / `Save Workflow Baseline` UI strings on the live host.
- Latest B7 validation passed: 74 focused archive/API/static/operator tests, 732 full unit tests, 73 script static checks, `node --check src/web/app.js`, live deploy completed, remote ops smoke passed 9 checks, and remote B7 smoke confirmed persisted archive defaults, cross-profile preset retrieval, and deployed `Save Digest Defaults` / `Use Saved Digest Defaults` / `Save Workflow Defaults` / `Use Saved Workflow Defaults` UI strings on the live host.
- Latest E12 validation passed: 16 focused external-context/static tests, 732 full unit tests, 73 script static checks, `node --check src/web/app.js`, live deploy completed, remote ops smoke passed 9 checks, and remote E12 smoke confirmed screenshot readiness summary, capture-surface screenshot status fields, and deployed `Open Screenshot Surface` UI strings on the live host.
- Refreshed `docs/AI_ICT_??????????????????????2026-05-14_v1.md` after closing Phases A-E; the active remaining mainline backlog is now archive long-range trend deepening, archive deep-retrieval deepening, external context deep visual evidence extensions, and workflow archive deep bridge extensions.
- Real-live order submission remains intentionally disabled because the OKX order adapter is still not a real-live submitter.
## Exact Next Step
Use `docs/AI_ICT_???????????????????2026-05-14_v1.md` as the completed enhancement record for Phases A-E. The active backlog source is now `docs/AI_ICT_??????????????????????2026-05-14_v1.md`. `B5 archive keyword search / export starter slice`, `W7 workflow archive replay / handoff / export bridge starter slice`, `E10 external context visual evidence / provider health starter slice`, `O1 archive long-range trend / baseline pin starter slice`, `B6 archive deep retrieval / structured export follow-up starter slice`, `E11 external context deep visual evidence / capture surface follow-up starter slice`, `W8 workflow archive deep bridge extension starter slice`, and `O2 archive long-range trend deepening / baseline persistence starter slice` are now closed. Start from `B7 archive deep retrieval deepening / preset persistence starter slice`, keep work on the analysis-assistant mainline only, and continue treating real-trading scope as deferred. PXX remains closed and you still must not create P25/P26 automatically or enable real trading.
Use `docs/AI_ICT_???????????????????2026-05-14_v1.md` as the completed enhancement record for Phases A-E. The active backlog source is now `docs/AI_ICT_??????????????????????2026-05-14_v1.md`. `B5 archive keyword search / export starter slice`, `W7 workflow archive replay / handoff / export bridge starter slice`, `E10 external context visual evidence / provider health starter slice`, `O1 archive long-range trend / baseline pin starter slice`, `B6 archive deep retrieval / structured export follow-up starter slice`, `E11 external context deep visual evidence / capture surface follow-up starter slice`, `W8 workflow archive deep bridge extension starter slice`, `O2 archive long-range trend deepening / baseline persistence starter slice`, `B7 archive deep retrieval deepening / preset persistence starter slice`, and `E12 external context visual evidence extension / screenshot readiness starter slice` are now closed. Start from `W9 workflow archive deep bridge extension / bundle export persistence slice`, keep work on the analysis-assistant mainline only, and continue treating real-trading scope as deferred. PXX remains closed and you still must not create P25/P26 automatically or enable real trading.
## Validation Commands Used Recently
@@ -777,3 +779,5 @@ D:\ai-exchange\.venv\Scripts\python.exe -m unittest tests.unit.test_run_automati
D:\ai-exchange\.venv\Scripts\python.exe scripts\run_automation_execution_closure_gate.py
```
+4 -1
View File
@@ -111,7 +111,9 @@
- [x] E11 external context deep visual evidence / capture surface follow-up starter slice
- [x] W8 workflow archive deep bridge extension starter slice
- [x] O2 archive long-range trend deepening / baseline persistence starter slice
- [ ] B7 archive deep retrieval deepening / preset persistence starter slice
- [x] B7 archive deep retrieval deepening / preset persistence starter slice
- [x] E12 external context visual evidence extension / screenshot readiness starter slice
- [ ] W9 workflow archive deep bridge extension / bundle export persistence slice
- [x] A1 independent preferences surface / dedicated settings section
- [x] A2 preference profile create / duplicate / rename / delete
@@ -845,3 +847,4 @@
- [ ] Long-term target: complete `Real-time Assisted Trading V1` without enabling default live auto-trading
@@ -124,5 +124,27 @@
- 下一刀最小闭环切片改为:
- `B7 archive deep retrieval deepening / preset persistence starter slice`
## 9. 2026-05-15 B7 Addendum
- `B7 archive deep retrieval deepening / preset persistence starter slice` 已完成。
- 当前已具备:
- archive query preset 持久化 starter fields
- cross-profile archive retrieval starter summary
- deeper structured export summary starter fields
- cockpit `Save Digest Defaults / Use Saved Digest Defaults / Save Workflow Defaults / Use Saved Workflow Defaults`
- 下一刀最小闭环切片改为:
- `E12 external context visual evidence extension / screenshot readiness starter slice`
## 10. 2026-05-15 E12 Addendum
- `E12 external context visual evidence extension / screenshot readiness starter slice` 已完成。
- 当前已具备:
- screenshot readiness summary
- capture surface screenshot status / browser capture status starter fields
- cockpit `Open Screenshot Surface`
- 下一刀最小闭环切片改为:
- `W9 workflow archive deep bridge extension / bundle export persistence slice`
- A-E 璁″垝宸茬粡鍏ㄩ儴瀹屾垚锛屼笉璇ュ啀浣滀负涓?backlog 閲嶅鎺ㄨ繘銆?- 褰撳墠鐪熸鏈畬鎴愮殑锛屽凡缁忎粠鈥滃熀纭€浜у搧鍖栤€濆垏鎹㈠埌鈥渁rchive 娣卞寲銆乪xternal evidence 寮哄寲銆佽繍钀ラ暱鏈熻鍥锯€濊繖涓€绾с€?- 鐪熷疄浜ゆ槗銆乴ive adapter銆佷笅鍗曟潈闄愮浉鍏冲唴瀹圭户缁繚鎸佸欢鏈燂紝涓嶅簲娣峰洖褰撳墠鍒嗘瀽鍔╂墜涓荤嚎銆?
+36
View File
@@ -453,6 +453,11 @@ def get_operator_digest_history_archive(
items,
persisted_snapshot_id=baseline_state.get("digest_snapshot_id"),
)
profile_scope_summary = _build_archive_profile_scope_summary(
items=items,
profile_scope=normalized_scope,
selected_profile_key=profile_key,
)
return {
"status": "active" if items else "idle",
"instrument_id": instrument_id,
@@ -462,6 +467,7 @@ def get_operator_digest_history_archive(
"query_preset_summary": effective.get("query_preset_summary"),
"available_query_presets": _digest_archive_available_query_presets(),
"available_profile_scopes": ["selected_profile", "all_profiles"],
"profile_scope_summary": profile_scope_summary,
"items": items,
"item_count": len(items),
"offset": offset,
@@ -580,6 +586,7 @@ def export_operator_digest_history_archive(
"profile_key": profile_key,
"profile_scope": archive.get("profile_scope"),
"query_preset": archive.get("query_preset"),
"profile_scope_summary": dict(archive.get("profile_scope_summary") or {}),
"filters": dict(archive.get("filters") or {}),
"item_count": archive.get("item_count") or 0,
"items": list(archive.get("items") or []),
@@ -639,6 +646,11 @@ def get_operator_workflow_archive(
items,
persisted_snapshot_id=baseline_state.get("workflow_snapshot_id"),
)
profile_scope_summary = _build_archive_profile_scope_summary(
items=items,
profile_scope=normalized_scope,
selected_profile_key=profile_key,
)
return {
"status": "active" if items else "idle",
"instrument_id": instrument_id,
@@ -648,6 +660,7 @@ def get_operator_workflow_archive(
"query_preset_summary": effective.get("query_preset_summary"),
"available_query_presets": _workflow_archive_available_query_presets(),
"available_profile_scopes": ["selected_profile", "all_profiles"],
"profile_scope_summary": profile_scope_summary,
"items": items,
"item_count": len(items),
"offset": offset,
@@ -771,6 +784,7 @@ def export_operator_workflow_archive(
"profile_key": profile_key,
"profile_scope": archive.get("profile_scope"),
"query_preset": archive.get("query_preset"),
"profile_scope_summary": dict(archive.get("profile_scope_summary") or {}),
"filters": dict(archive.get("filters") or {}),
"item_count": archive.get("item_count") or 0,
"items": list(archive.get("items") or []),
@@ -1104,6 +1118,24 @@ def _fetch_workflow_history_rows_by_ids(
return filtered
def _build_archive_profile_scope_summary(*, items: list[dict], profile_scope: str, selected_profile_key: str) -> dict:
profile_counts: dict[str, int] = {}
for item in items:
profile_key = str(item.get("profile_key") or "unknown")
profile_counts[profile_key] = int(profile_counts.get(profile_key) or 0) + 1
most_active_profile = None
if profile_counts:
most_active_profile = sorted(profile_counts.items(), key=lambda row: (-row[1], row[0]))[0][0]
return {
"status": "active" if items else "idle",
"scope": profile_scope,
"selected_profile_key": selected_profile_key,
"profile_count": len(profile_counts),
"profile_counts": profile_counts,
"most_active_profile": most_active_profile,
}
def _normalize_digest_history_row(row: dict) -> dict:
payload = dict(row.get("payload") or {})
return {
@@ -1371,6 +1403,8 @@ def _build_digest_archive_structured_export_summary(archive: dict, compare: dict
kind_counts[history_kind] = int(kind_counts.get(history_kind) or 0) + 1
return {
"status": archive.get("status") or "idle",
"profile_scope": archive.get("profile_scope"),
"query_preset": archive.get("query_preset"),
"profile_count": len(profile_counts),
"profile_counts": profile_counts,
"history_kind_counts": kind_counts,
@@ -1394,6 +1428,8 @@ def _build_workflow_archive_structured_export_summary(archive: dict, compare: di
phase_counts[phase_key] = int(phase_counts.get(phase_key) or 0) + 1
return {
"status": archive.get("status") or "idle",
"profile_scope": archive.get("profile_scope"),
"query_preset": archive.get("query_preset"),
"profile_count": len(profile_counts),
"profile_counts": profile_counts,
"session_counts": session_counts,
@@ -6,6 +6,7 @@ import hashlib
import json
from typing import Optional
from src.config.settings import settings
from src.repositories.decision_packet_repository import DecisionPacketRepository
from src.repositories.decision_assistant_repository import DecisionAssistantRepository
from src.repositories.market_session_repository import MarketSessionRepository
@@ -682,6 +683,25 @@ def _decision_assistant_archive_baselines(persisted_digest_preference: dict) ->
}
def _decision_assistant_archive_defaults(persisted_digest_preference: dict) -> dict:
payload = _decision_assistant_preference_payload(persisted_digest_preference)
defaults = dict(payload.get("archive_defaults") or {})
digest_defaults = dict(defaults.get("digest") or {})
workflow_defaults = dict(defaults.get("workflow") or {})
status = "persisted" if digest_defaults or workflow_defaults else "not_saved"
return {
"status": status,
"digest": {
"profile_scope": digest_defaults.get("profile_scope") or "selected_profile",
"query_preset": digest_defaults.get("query_preset") or "",
},
"workflow": {
"profile_scope": workflow_defaults.get("profile_scope") or "selected_profile",
"query_preset": workflow_defaults.get("query_preset") or "",
},
}
def _decision_assistant_importance_template_thresholds(*, persisted_digest_preference: dict) -> dict:
payload = _decision_assistant_preference_payload(persisted_digest_preference)
thresholds = dict(payload.get("importance_templates") or {})
@@ -2273,6 +2293,7 @@ def _build_decision_assistant_external_context_summary(
provider_health_cards=provider_health_cards,
)
capture_surface_summary = _build_external_context_capture_surface_summary(capture_surface_cards)
screenshot_readiness_summary = _build_external_context_screenshot_readiness_summary(capture_surface_cards)
chart_adapter_mode = _external_context_chart_adapter_mode(chart_readonly_sources)
return {
"status": "active" if active_events or external_waiting_items or council_watch_items or operator_questions or next_confirmation_targets or exchange_events or announcements else "idle",
@@ -2325,6 +2346,7 @@ def _build_decision_assistant_external_context_summary(
"capture_surface_card_count": len(capture_surface_cards),
"capture_surface_status_counts": _external_context_inventory_status_counts(capture_surface_cards),
"capture_surface_summary": capture_surface_summary,
"screenshot_readiness_summary": screenshot_readiness_summary,
"source_cards": [
{
"source_type": "news_provider",
@@ -4028,6 +4050,20 @@ def _external_context_capture_readiness(*, target_url: str | None, embed_url: st
return "no_surface"
def _external_context_screenshot_capture_status(*, capture_supported: bool) -> str:
if not capture_supported:
return "capture_not_supported"
if not bool(settings.visual_browser_capture_enabled):
return "browser_capture_disabled"
if str(settings.visual_browser_capture_browser_path or "").strip():
return "configured_browser_ready"
return "browser_autodiscovery_needed"
def _external_context_screenshot_embed_status(*, embed_url: str | None) -> str:
return "embed_ready" if str(embed_url or "").strip() else "embed_not_available"
def _build_external_context_capture_surface_cards(
*,
visual_reference_cards: list[dict],
@@ -4060,6 +4096,17 @@ def _build_external_context_capture_surface_cards(
"target_url": target_url,
"embed_url": embed_url,
"evidence_bridge_status": "active" if target_url or embed_url else "idle",
"screenshot_capture_status": _external_context_screenshot_capture_status(
capture_supported=capture_supported,
),
"screenshot_embed_status": _external_context_screenshot_embed_status(embed_url=embed_url),
"browser_capture_enabled": bool(settings.visual_browser_capture_enabled),
"browser_path_configured": bool(str(settings.visual_browser_capture_browser_path or "").strip()),
"screenshot_timeout_seconds": int(settings.visual_browser_capture_timeout_seconds or 0),
"screenshot_viewport": {
"width": int(settings.visual_browser_capture_viewport_width or 0),
"height": int(settings.visual_browser_capture_viewport_height or 0),
},
"capture_surface_prompt": item.get("visual_prompt") or item.get("next_visual_prompt"),
}
)
@@ -4100,6 +4147,60 @@ def _build_external_context_capture_surface_summary(cards: list[dict]) -> dict:
}
def _build_external_context_screenshot_readiness_summary(cards: list[dict]) -> dict:
if not cards:
return {
"status": "idle",
"browser_capture_enabled": bool(settings.visual_browser_capture_enabled),
"browser_path_configured": bool(str(settings.visual_browser_capture_browser_path or "").strip()),
"screenshot_ready_count": 0,
"autodiscovery_needed_count": 0,
"embed_ready_count": 0,
"next_screenshot_target": None,
"next_screenshot_prompt": None,
}
screenshot_ready_count = sum(
1
for item in cards
if str(item.get("screenshot_capture_status") or "") == "configured_browser_ready"
)
autodiscovery_needed_count = sum(
1
for item in cards
if str(item.get("screenshot_capture_status") or "") == "browser_autodiscovery_needed"
)
embed_ready_count = sum(
1
for item in cards
if str(item.get("screenshot_embed_status") or "") == "embed_ready"
)
next_target = next(
(
dict(item)
for item in cards
if str(item.get("screenshot_capture_status") or "") in {"configured_browser_ready", "browser_autodiscovery_needed"}
),
dict(cards[0] or {}),
)
return {
"status": "active",
"browser_capture_enabled": bool(settings.visual_browser_capture_enabled),
"browser_path_configured": bool(str(settings.visual_browser_capture_browser_path or "").strip()),
"screenshot_ready_count": screenshot_ready_count,
"autodiscovery_needed_count": autodiscovery_needed_count,
"embed_ready_count": embed_ready_count,
"viewport": {
"width": int(settings.visual_browser_capture_viewport_width or 0),
"height": int(settings.visual_browser_capture_viewport_height or 0),
},
"timeout_seconds": int(settings.visual_browser_capture_timeout_seconds or 0),
"next_screenshot_target": next_target.get("title") or next_target.get("provider_name"),
"next_screenshot_status": next_target.get("screenshot_capture_status"),
"next_screenshot_prompt": next_target.get("capture_surface_prompt"),
"next_screenshot_url": next_target.get("target_url") or next_target.get("embed_url"),
}
def _build_external_context_visual_reference_cards(
*,
macro_news_provider_inventory: list[dict],
@@ -4688,6 +4789,7 @@ def _build_digest_preference_profile(
) -> dict:
preference_payload = _decision_assistant_preference_payload(persisted_digest_preference)
archive_baselines = _decision_assistant_archive_baselines(persisted_digest_preference)
archive_defaults = _decision_assistant_archive_defaults(persisted_digest_preference)
threshold_overrides = dict(preference_payload.get("importance_templates") or {})
template_counts = _digest_importance_template_counts(personalized_reminders)
preferred_template = next(
@@ -4744,6 +4846,7 @@ def _build_digest_preference_profile(
"available_profiles": available_profiles,
"focus_candidate_id": dict(focus_candidate or {}).get("id"),
"archive_baselines": archive_baselines,
"archive_defaults": archive_defaults,
"high_value_only_recommended": high_value_count > 0,
"profile_reason": _digest_preference_profile_reason(
preferred_template=preferred_template,
@@ -9133,17 +9236,19 @@ def _build_decision_assistant_evidence_chain(
},
)
capture_surface_summary = dict((external_context_summary or {}).get("capture_surface_summary") or {})
screenshot_readiness_summary = dict((external_context_summary or {}).get("screenshot_readiness_summary") or {})
if capture_surface_summary.get("status") == "active":
chain.append(
{
"title": "External Visual Evidence",
"status": capture_surface_summary.get("next_capture_readiness") or "active",
"status": screenshot_readiness_summary.get("next_screenshot_status") or capture_surface_summary.get("next_capture_readiness") or "active",
"detail": (
f"{capture_surface_summary.get('next_capture_target') or 'external surface'} | "
f"capture_ready={capture_surface_summary.get('capture_ready_count') or 0} | "
f"embed_ready={capture_surface_summary.get('embed_ready_count') or 0}"
f"embed_ready={capture_surface_summary.get('embed_ready_count') or 0} | "
f"screenshot_ready={screenshot_readiness_summary.get('screenshot_ready_count') or 0}"
),
"reason": capture_surface_summary.get("next_capture_prompt") or "external_visual_capture_surface",
"reason": screenshot_readiness_summary.get("next_screenshot_prompt") or capture_surface_summary.get("next_capture_prompt") or "external_visual_capture_surface",
}
)
return chain
+144 -8
View File
@@ -427,6 +427,10 @@ function buildAssistantPreferenceDraft(summary) {
history_window_hours: Number(profile.history_window_hours || 24),
importance_templates: templates,
focus_candidate_id: profile.focus_candidate_id ?? summary?.focus_workspace_context?.focus_candidate_id ?? null,
archive_defaults: profile.archive_defaults || {
digest: { profile_scope: 'selected_profile', query_preset: '' },
workflow: { profile_scope: 'selected_profile', query_preset: '' },
},
};
}
@@ -466,6 +470,19 @@ function updateAssistantPreferenceTemplateDraft(template, threshold) {
updateAssistantPreferenceDraft({ importance_templates: nextTemplates });
}
function mergeAssistantArchiveDefaults(summary, patch = {}) {
const profile = summary?.digest_surface?.digest_preference_profile || {};
const currentDefaults = {
digest: { profile_scope: 'selected_profile', query_preset: '' },
workflow: { profile_scope: 'selected_profile', query_preset: '' },
...((assistantPreferenceDraft && assistantPreferenceDraft.archive_defaults) || profile.archive_defaults || {}),
};
return {
...currentDefaults,
...(patch || {}),
};
}
function resetAssistantPreferenceDraftToServer(summary) {
assistantPreferenceDraft = buildAssistantPreferenceDraft(summary);
assistantPreferenceDraftDirty = false;
@@ -972,6 +989,7 @@ function renderDecisionAssistant(summary) {
: (digestSurface?.digest_archive_compare || summary.digest_archive_compare || null);
const digestArchiveLongRangeSummary = digestArchiveData?.long_range_trend_summary || digestSurface?.digest_history_archive?.long_range_trend_summary || null;
const digestArchiveBaselineSummary = digestArchiveData?.baseline_pin_summary || digestSurface?.digest_history_archive?.baseline_pin_summary || null;
const digestArchiveSavedDefaults = digestPreferenceProfile?.archive_defaults?.digest || { profile_scope: 'selected_profile', query_preset: '' };
const workflowArchiveData = window.__assistantWorkflowArchiveResponse?.instrument_id === Number(instrumentIdInput.value || 1)
? window.__assistantWorkflowArchiveResponse
: summary.workflow_artifact_summary?.workflow_history_archive;
@@ -980,6 +998,7 @@ function renderDecisionAssistant(summary) {
: (summary.workflow_artifact_summary?.workflow_archive_compare || summary.workflow_archive_compare || null);
const workflowArchiveLongRangeSummary = workflowArchiveData?.long_range_trend_summary || summary.workflow_artifact_summary?.workflow_history_archive?.long_range_trend_summary || null;
const workflowArchiveBaselineSummary = workflowArchiveData?.baseline_pin_summary || summary.workflow_artifact_summary?.workflow_history_archive?.baseline_pin_summary || null;
const workflowArchiveSavedDefaults = digestPreferenceProfile?.archive_defaults?.workflow || { profile_scope: 'selected_profile', query_preset: '' };
const workflowArchiveOperationsSummary = workflowArchiveData?.operations_summary
|| summary.workflow_artifact_summary?.workflow_archive_operations_summary
|| summary.workflow_archive_operations_summary
@@ -1627,6 +1646,7 @@ function renderDecisionAssistant(summary) {
profile_label: assistantPreferenceDraft?.profile_label || selectedProfileKey,
history_window_hours: assistantPreferenceDraft?.history_window_hours || 24,
importance_templates: { ...((assistantPreferenceDraft && assistantPreferenceDraft.importance_templates) || {}) },
archive_defaults: { ...((assistantPreferenceDraft && assistantPreferenceDraft.archive_defaults) || {}) },
},
});
if (!saved) {
@@ -1797,7 +1817,7 @@ function renderDecisionAssistant(summary) {
['selected_profile', 'all_profiles'].forEach((scope) => {
const option = createElement('option', '', scope);
option.value = scope;
option.selected = scope === (assistantDigestArchiveState.profile_scope || 'selected_profile');
option.selected = scope === (assistantDigestArchiveState.profile_scope || digestArchiveSavedDefaults.profile_scope || 'selected_profile');
digestArchiveScopeSelect.appendChild(option);
});
digestArchiveScopeField.appendChild(digestArchiveScopeSelect);
@@ -1809,7 +1829,7 @@ function renderDecisionAssistant(summary) {
[{ preset_id: '', label: 'custom' }, ...((digestArchiveData?.available_query_presets || []))].forEach((preset) => {
const option = createElement('option', '', preset.label || preset.preset_id || 'custom');
option.value = preset.preset_id || '';
option.selected = (preset.preset_id || '') === (assistantDigestArchiveState.query_preset || '');
option.selected = (preset.preset_id || '') === (assistantDigestArchiveState.query_preset || digestArchiveSavedDefaults.query_preset || '');
digestArchivePresetSelect.appendChild(option);
});
digestArchivePresetField.appendChild(digestArchivePresetSelect);
@@ -1997,6 +2017,52 @@ function renderDecisionAssistant(summary) {
await refreshDashboard();
},
},
{
label: 'Save Digest Defaults',
onClick: async () => {
const saved = await persistAssistantDigestPreferenceServer({
profileKey: selectedProfileKey,
digestMode: assistantPreferenceDraft?.digest_mode || digestPreferenceProfile?.persisted_digest_mode || 'all_reminders',
preferredHistoryKind: assistantPreferenceDraft?.preferred_history_kind === 'all'
? null
: (assistantPreferenceDraft?.preferred_history_kind || digestPreferenceProfile?.preferred_history_kind || null),
importanceThreshold: assistantPreferenceDraft?.importance_threshold || digestPreferenceProfile?.importance_threshold || 70,
payload: {
profile_label: assistantPreferenceDraft?.profile_label || digestPreferenceProfile?.profile_label || selectedProfileKey,
history_window_hours: assistantPreferenceDraft?.history_window_hours || digestPreferenceProfile?.history_window_hours || 24,
importance_templates: { ...((assistantPreferenceDraft && assistantPreferenceDraft.importance_templates) || {}) },
archive_defaults: mergeAssistantArchiveDefaults(summary, {
digest: {
profile_scope: digestArchiveScopeSelect.value || 'selected_profile',
query_preset: digestArchivePresetSelect.value || '',
},
}),
},
});
if (!saved) {
showErrorPanel('digest_archive_defaults_save_failed');
return;
}
assistantPreferenceDraft = {
...(assistantPreferenceDraft || {}),
archive_defaults: mergeAssistantArchiveDefaults(summary, {
digest: {
profile_scope: digestArchiveScopeSelect.value || 'selected_profile',
query_preset: digestArchivePresetSelect.value || '',
},
}),
};
await refreshDashboard();
},
},
{
label: 'Use Saved Digest Defaults',
onClick: () => {
assistantDigestArchiveState.profile_scope = digestArchiveSavedDefaults.profile_scope || 'selected_profile';
assistantDigestArchiveState.query_preset = digestArchiveSavedDefaults.query_preset || '';
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
},
},
{
label: 'Clear Archive Filters',
onClick: () => {
@@ -2025,9 +2091,10 @@ function renderDecisionAssistant(summary) {
digestArchivePanel.appendChild(
buildInfoGrid([
['archive query profile', assistantDigestArchiveState.profile_key || selectedProfileKey],
['archive profile scope', assistantDigestArchiveState.profile_scope || 'selected_profile'],
['archive preset', assistantDigestArchiveState.query_preset || 'custom'],
['archive profile scope', assistantDigestArchiveState.profile_scope || digestArchiveSavedDefaults.profile_scope || 'selected_profile'],
['archive preset', assistantDigestArchiveState.query_preset || digestArchiveSavedDefaults.query_preset || 'custom'],
['archive preset summary', digestArchiveData?.query_preset_summary || 'custom_filters'],
['saved archive defaults', `${digestArchiveSavedDefaults.profile_scope || 'selected_profile'} / ${digestArchiveSavedDefaults.query_preset || 'custom'}`],
['archive query mode', assistantDigestArchiveState.digest_mode || 'all'],
['archive query kind', assistantDigestArchiveState.preferred_history_kind || 'all'],
['archive query text', assistantDigestArchiveState.query_text || 'n/a'],
@@ -2611,7 +2678,7 @@ function renderDecisionAssistant(summary) {
['selected_profile', 'all_profiles'].forEach((scope) => {
const option = createElement('option', '', scope);
option.value = scope;
option.selected = scope === (assistantWorkflowArchiveState.profile_scope || 'selected_profile');
option.selected = scope === (assistantWorkflowArchiveState.profile_scope || workflowArchiveSavedDefaults.profile_scope || 'selected_profile');
workflowScopeSelect.appendChild(option);
});
workflowScopeField.appendChild(workflowScopeSelect);
@@ -2623,7 +2690,7 @@ function renderDecisionAssistant(summary) {
[{ preset_id: '', label: 'custom' }, ...((workflowArchiveData?.available_query_presets || []))].forEach((preset) => {
const option = createElement('option', '', preset.label || preset.preset_id || 'custom');
option.value = preset.preset_id || '';
option.selected = (preset.preset_id || '') === (assistantWorkflowArchiveState.query_preset || '');
option.selected = (preset.preset_id || '') === (assistantWorkflowArchiveState.query_preset || workflowArchiveSavedDefaults.query_preset || '');
workflowPresetSelect.appendChild(option);
});
workflowPresetField.appendChild(workflowPresetSelect);
@@ -2820,6 +2887,52 @@ function renderDecisionAssistant(summary) {
await refreshDashboard();
},
},
{
label: 'Save Workflow Defaults',
onClick: async () => {
const saved = await persistAssistantDigestPreferenceServer({
profileKey: selectedProfileKey,
digestMode: assistantPreferenceDraft?.digest_mode || digestPreferenceProfile?.persisted_digest_mode || 'all_reminders',
preferredHistoryKind: assistantPreferenceDraft?.preferred_history_kind === 'all'
? null
: (assistantPreferenceDraft?.preferred_history_kind || digestPreferenceProfile?.preferred_history_kind || null),
importanceThreshold: assistantPreferenceDraft?.importance_threshold || digestPreferenceProfile?.importance_threshold || 70,
payload: {
profile_label: assistantPreferenceDraft?.profile_label || digestPreferenceProfile?.profile_label || selectedProfileKey,
history_window_hours: assistantPreferenceDraft?.history_window_hours || digestPreferenceProfile?.history_window_hours || 24,
importance_templates: { ...((assistantPreferenceDraft && assistantPreferenceDraft.importance_templates) || {}) },
archive_defaults: mergeAssistantArchiveDefaults(summary, {
workflow: {
profile_scope: workflowScopeSelect.value || 'selected_profile',
query_preset: workflowPresetSelect.value || '',
},
}),
},
});
if (!saved) {
showErrorPanel('workflow_archive_defaults_save_failed');
return;
}
assistantPreferenceDraft = {
...(assistantPreferenceDraft || {}),
archive_defaults: mergeAssistantArchiveDefaults(summary, {
workflow: {
profile_scope: workflowScopeSelect.value || 'selected_profile',
query_preset: workflowPresetSelect.value || '',
},
}),
};
await refreshDashboard();
},
},
{
label: 'Use Saved Workflow Defaults',
onClick: () => {
assistantWorkflowArchiveState.profile_scope = workflowArchiveSavedDefaults.profile_scope || 'selected_profile';
assistantWorkflowArchiveState.query_preset = workflowArchiveSavedDefaults.query_preset || '';
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
},
},
{
label: 'Clear Workflow Filters',
onClick: () => {
@@ -2849,9 +2962,10 @@ function renderDecisionAssistant(summary) {
workflowArchivePanel.appendChild(
buildInfoGrid([
['workflow query profile', assistantWorkflowArchiveState.profile_key || selectedProfileKey],
['workflow profile scope', assistantWorkflowArchiveState.profile_scope || 'selected_profile'],
['workflow preset', assistantWorkflowArchiveState.query_preset || 'custom'],
['workflow profile scope', assistantWorkflowArchiveState.profile_scope || workflowArchiveSavedDefaults.profile_scope || 'selected_profile'],
['workflow preset', assistantWorkflowArchiveState.query_preset || workflowArchiveSavedDefaults.query_preset || 'custom'],
['workflow preset summary', workflowArchiveData?.query_preset_summary || 'custom_filters'],
['saved workflow defaults', `${workflowArchiveSavedDefaults.profile_scope || 'selected_profile'} / ${workflowArchiveSavedDefaults.query_preset || 'custom'}`],
['workflow query session', assistantWorkflowArchiveState.current_session || 'all'],
['workflow query phase', assistantWorkflowArchiveState.next_phase || 'all'],
['workflow query artifact', assistantWorkflowArchiveState.next_artifact_kind || 'all'],
@@ -3387,6 +3501,12 @@ function renderDecisionAssistant(summary) {
['capture surface states', formatJsonInline(externalContext.capture_surface_status_counts || {})],
['capture next target', externalContext.capture_surface_summary?.next_capture_target || 'n/a'],
['capture next prompt', externalContext.capture_surface_summary?.next_capture_prompt || 'n/a'],
['screenshot readiness', externalContext.screenshot_readiness_summary?.status || 'n/a'],
['screenshot ready', String(externalContext.screenshot_readiness_summary?.screenshot_ready_count ?? 0)],
['autodiscovery needed', String(externalContext.screenshot_readiness_summary?.autodiscovery_needed_count ?? 0)],
['screenshot embed ready', String(externalContext.screenshot_readiness_summary?.embed_ready_count ?? 0)],
['screenshot next target', externalContext.screenshot_readiness_summary?.next_screenshot_target || 'n/a'],
['screenshot next prompt', externalContext.screenshot_readiness_summary?.next_screenshot_prompt || 'n/a'],
['visual references', String(externalContext.visual_reference_card_count ?? 0)],
['visual prompt', externalContext.visual_reference_next_prompt || 'n/a'],
['visual evidence bridge', externalContext.visual_evidence_bridge_summary?.status || 'n/a'],
@@ -3423,6 +3543,16 @@ function renderDecisionAssistant(summary) {
])
);
}
if (externalContext.screenshot_readiness_summary?.next_screenshot_url) {
decisionAssistantPanel.appendChild(
buildActionBar([
{
label: 'Open Screenshot Surface',
onClick: () => openExternalContextLink(externalContext.screenshot_readiness_summary?.next_screenshot_url),
},
])
);
}
const sourceCards = externalContext.source_cards || [];
if (sourceCards.length) {
const sourceGrid = createElement('div', 'cards');
@@ -3518,8 +3648,14 @@ function renderDecisionAssistant(summary) {
[
['group', item.surface_group || 'n/a'],
['capture', String(Boolean(item.capture_supported))],
['screenshot status', item.screenshot_capture_status || 'n/a'],
['embed status', item.screenshot_embed_status || 'n/a'],
['embed', item.embed_ready ? 'ready' : 'n/a'],
['bridge', item.evidence_bridge_status || 'n/a'],
['browser capture', String(Boolean(item.browser_capture_enabled))],
['browser configured', String(Boolean(item.browser_path_configured))],
['screenshot timeout', String(item.screenshot_timeout_seconds ?? 'n/a')],
['viewport', formatJsonInline(item.screenshot_viewport || {})],
['target', item.target_url || 'n/a'],
['embed url', item.embed_url || 'n/a'],
['prompt', item.capture_surface_prompt || 'n/a'],
+8
View File
@@ -157,10 +157,14 @@ class ApiStaticServerTests(unittest.TestCase):
"provider health cards",
"Open Visual Bridge",
"Open Capture Surface",
"Open Screenshot Surface",
"Open Target",
"Open Embed",
"Open Capture Target",
"Open Capture Embed",
"screenshot readiness",
"screenshot status",
"browser capture",
"archive trend",
"archive trend story",
"archive baseline",
@@ -169,6 +173,10 @@ class ApiStaticServerTests(unittest.TestCase):
"Compare To Baseline",
"Save Baseline",
"Save Workflow Baseline",
"Save Digest Defaults",
"Use Saved Digest Defaults",
"Save Workflow Defaults",
"Use Saved Workflow Defaults",
"workflow long-range",
"workflow baseline",
"workflow trend story",
@@ -657,6 +657,8 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
self.assertGreaterEqual(summary["external_context_summary"]["capture_surface_card_count"], 3)
self.assertIn("capture_surface_summary", summary["external_context_summary"])
self.assertEqual(summary["external_context_summary"]["capture_surface_summary"]["status"], "active")
self.assertIn("screenshot_readiness_summary", summary["external_context_summary"])
self.assertEqual(summary["external_context_summary"]["screenshot_readiness_summary"]["status"], "active")
self.assertIn("visual_reference_cards", summary["external_context_summary"])
self.assertGreaterEqual(summary["external_context_summary"]["visual_reference_card_count"], 3)
self.assertIsNotNone(summary["external_context_summary"]["visual_reference_next_prompt"])