Add focus workspace continuity
This commit is contained in:
+6
-3
@@ -2,7 +2,7 @@
|
||||
|
||||
## Current Goal
|
||||
|
||||
Automation execution-layer PXX remains closed and its known scope stays frozen. Under explicit user direction, the active product phase is a dense `Decision Assistant` operator workspace with grouped recall, broader replay baselines, note templates, quick-save shortcuts, read-only market context, and a continuous operator cockpit. This run closed F26/W5/H27 as a real backend persistence batch: digest preferences now persist on the server, digest snapshots archive into a history table, workflow artifacts archive into their own history table, cockpit reads back both persisted digest/workflow archives, and the front-end writes digest preference changes back through a dedicated API instead of local-only storage. Local validation passed, remote migrations `044_create_decision_assistant_digest_state.sql` and `045_create_decision_assistant_workflow_history.sql` were applied, the current working tree was deployed, and remote smoke confirmed the latest Decision Assistant fields on the live host.
|
||||
Automation execution-layer PXX remains closed and its known scope stays frozen. Under explicit user direction, the active product phase is a dense `Decision Assistant` operator workspace with grouped recall, broader replay baselines, note templates, quick-save shortcuts, read-only market context, and a continuous operator cockpit. This run closed H28 as a real focus-workspace batch: a new `focus_workspace_context` now carries the current focus title/session/model/tier plus cross-section workspace prompts, watchlist cards can pin the active focus locally, and the cockpit exposes a dedicated `Focus Workspace` section that bridges Watchlist, Evidence, Review Entry, Digest, and Replay around the same object. Local validation passed, the current working tree was deployed, and remote smoke confirmed the latest Decision Assistant fields on the live host.
|
||||
|
||||
## Current Branch
|
||||
|
||||
@@ -133,6 +133,7 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
|
||||
- latest_decision_assistant_digest_archive_slice: persisted_digest_archive_visibility
|
||||
- latest_decision_assistant_workflow_persistence_slice: workflow_history_archive_persistence
|
||||
- latest_decision_assistant_workflow_archive_slice: persisted_workflow_archive_visibility
|
||||
- latest_decision_assistant_focus_workspace_slice: focus_workspace_context
|
||||
- latest_decision_assistant_summary_status: implemented
|
||||
- latest_decision_assistant_change_summary_status: implemented
|
||||
- latest_decision_assistant_evidence_status: implemented
|
||||
@@ -250,10 +251,11 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
|
||||
- latest_decision_assistant_digest_archive_status: implemented
|
||||
- latest_decision_assistant_workflow_persistence_status: implemented
|
||||
- latest_decision_assistant_workflow_archive_status: implemented
|
||||
- latest_decision_assistant_focus_workspace_status: implemented
|
||||
- latest_decision_assistant_workbench_status: visible_local_smoke
|
||||
- latest_remote_decision_assistant_deploy_status: deployed_current_head
|
||||
- latest_remote_decision_assistant_smoke_status: passed
|
||||
- latest_remote_decision_assistant_review_packet_status: digest_and_workflow_archives_visible
|
||||
- latest_remote_decision_assistant_review_packet_status: focus_workspace_context_visible
|
||||
- latest_p7_selector_choice: continue_periodic_analysis_decision_monitoring_with_maturity_rollup
|
||||
- latest_p14_slice: webhook_transport_retry
|
||||
- latest_p15_pxx_tasklist_status: ready
|
||||
@@ -670,11 +672,12 @@ Automation execution-layer PXX remains closed and its known scope stays frozen.
|
||||
- Latest W4/D25/F25/H25 validation passed: focused operator workspace tests, 68 related unit/API/static tests, 713 full unit tests, 73 script static checks, `node --check src/web/app.js`, deploy smoke passed 11 endpoints, remote ops smoke passed 9 checks, and remote field smoke confirmed `workflow_artifact_summary`, `digest_action_queue`, `learning_focus_matrix`, and `workspace_continuity_handoff` plus matching UI strings.
|
||||
- Latest C3/G20/H26 validation passed: focused operator workspace tests, 68 related unit/API/static tests, 713 full unit tests, 73 script static checks, `node --check src/web/app.js`, deploy smoke passed 11 endpoints, remote ops smoke passed 9 checks, and remote field smoke confirmed `watchlist focus matrix`, `review_entry_action_queue`, `review_entry_handoff_summary`, and watchlist-focus presence inside `assistant_loop_queue`.
|
||||
- Latest F26/W5/H27 validation passed: focused operator workspace/API/deploy tests, 75 related unit/API/static tests, 715 full unit tests, 73 script static checks, `node --check src/web/app.js`, remote migrations `044_create_decision_assistant_digest_state.sql` and `045_create_decision_assistant_workflow_history.sql` applied successfully, deploy completed, remote ops smoke passed 9 checks, and remote field smoke confirmed `/trading/alerts/digest-preferences`, persisted `digest_preference_profile`, `digest_history_archive`, and `workflow_history_archive`.
|
||||
- Latest H28 validation passed: focused operator workspace tests, 69 related unit/API/static tests, 715 full unit tests, 73 script static checks, `node --check src/web/app.js`, deploy completed, remote ops smoke passed 9 checks, and remote field smoke confirmed `focus_workspace_context` plus the deployed `Focus Workspace` UI.
|
||||
- Real-live order submission remains intentionally disabled because the OKX order adapter is still not a real-live submitter.
|
||||
|
||||
## Exact Next Step
|
||||
|
||||
F26/W5/H27 backend persistence batch is closed. External chart URLs remain optional reference enhancements and are not required for the current analysis-only loop. Internal non-trading backlog still exists, but PXX remains closed; do not create P25/P26 automatically, and do not enable real trading.
|
||||
H28 focus workspace batch is closed. External chart URLs remain optional reference enhancements and are not required for the current analysis-only loop. Internal non-trading backlog still exists, but PXX remains closed; do not create P25/P26 automatically, and do not enable real trading.
|
||||
|
||||
## Validation Commands Used Recently
|
||||
|
||||
|
||||
@@ -163,6 +163,7 @@
|
||||
- [x] F26 digest preference backend persistence / history archive slice
|
||||
- [x] W5 workflow artifact archive persistence slice
|
||||
- [x] H27 persisted digest archive / cockpit sync slice
|
||||
- [x] H28 focus workspace / cross-section current-focus sync slice
|
||||
- [x] P8 local closure complete
|
||||
- [x] P9 local closure complete
|
||||
- [x] P10 local closure complete
|
||||
@@ -289,6 +290,7 @@
|
||||
- [x] latest_decision_assistant_digest_archive_slice: persisted_digest_archive_visibility
|
||||
- [x] latest_decision_assistant_workflow_persistence_slice: workflow_history_archive_persistence
|
||||
- [x] latest_decision_assistant_workflow_archive_slice: persisted_workflow_archive_visibility
|
||||
- [x] latest_decision_assistant_focus_workspace_slice: focus_workspace_context
|
||||
- [x] latest_decision_assistant_summary_status: implemented
|
||||
- [x] latest_decision_assistant_change_summary_status: implemented
|
||||
- [x] latest_decision_assistant_evidence_status: implemented
|
||||
@@ -403,10 +405,11 @@
|
||||
- [x] latest_decision_assistant_digest_archive_status: implemented
|
||||
- [x] latest_decision_assistant_workflow_persistence_status: implemented
|
||||
- [x] latest_decision_assistant_workflow_archive_status: implemented
|
||||
- [x] latest_decision_assistant_focus_workspace_status: implemented
|
||||
- [x] latest_decision_assistant_workbench_status: visible_local_smoke
|
||||
- [x] latest_remote_decision_assistant_deploy_status: deployed_current_head
|
||||
- [x] latest_remote_decision_assistant_smoke_status: passed
|
||||
- [x] latest_remote_decision_assistant_review_packet_status: digest_and_workflow_archives_visible
|
||||
- [x] latest_remote_decision_assistant_review_packet_status: focus_workspace_context_visible
|
||||
- [x] latest_p7_selector_choice: continue_periodic_analysis_decision_monitoring_with_maturity_rollup
|
||||
- [x] latest_p14_slice: webhook_transport_retry
|
||||
- [x] latest_p15_pxx_tasklist_status: ready
|
||||
@@ -720,6 +723,8 @@
|
||||
- [x] Deployed C3/G20/H26 current tree and passed remote smoke: 11 deploy endpoints, 9 remote ops checks, and review-packet/static field smoke for `watchlist focus matrix`, `review_entry_action_queue`, `review_entry_handoff_summary`, and watchlist-focus presence inside `assistant_loop_queue`
|
||||
- [x] Completed F26/W5/H27 focused validation: operator workspace/API/deploy tests, 75 related unit/API/static tests, 715 full unit tests, 73 script static checks, and dashboard JS syntax
|
||||
- [x] Deployed F26/W5/H27 current tree, applied remote migrations `044_create_decision_assistant_digest_state.sql` and `045_create_decision_assistant_workflow_history.sql`, and passed remote smoke for `/trading/alerts/digest-preferences`, persisted `digest_preference_profile`, `digest_history_archive`, and `workflow_history_archive`
|
||||
- [x] Completed H28 focused validation: operator workspace tests, 69 related unit/API/static tests, 715 full unit tests, 73 script static checks, and dashboard JS syntax
|
||||
- [x] Deployed H28 current tree and passed remote smoke: 9 remote ops checks, `focus_workspace_context` field smoke, and deployed `Focus Workspace` UI visibility
|
||||
|
||||
## Blocked
|
||||
|
||||
|
||||
@@ -632,6 +632,13 @@ def build_decision_assistant_summary(
|
||||
continuous_workspace_rail=continuous_workspace_rail,
|
||||
assistant_loop_queue=assistant_loop_queue,
|
||||
)
|
||||
focus_workspace_context = _build_decision_assistant_focus_workspace_context(
|
||||
top_opportunities=assistant_top_opportunities,
|
||||
candidate_watchlist_summary=candidate_watchlist_summary,
|
||||
review_entrypoints=review_entrypoints,
|
||||
digest_surface=digest_surface,
|
||||
setup_learning_rollup=setup_learning_rollup,
|
||||
)
|
||||
return {
|
||||
"instrument_id": instrument_id,
|
||||
"timeframe": timeframe,
|
||||
@@ -675,6 +682,7 @@ def build_decision_assistant_summary(
|
||||
"assistant_loop_queue": assistant_loop_queue,
|
||||
"assistant_loop_summary": assistant_loop_summary,
|
||||
"workspace_continuity_handoff": workspace_continuity_handoff,
|
||||
"focus_workspace_context": focus_workspace_context,
|
||||
"continuous_workspace_rail": continuous_workspace_rail,
|
||||
"continuous_workspace_status": continuous_workspace_summary.get("status"),
|
||||
"continuous_workspace_summary": continuous_workspace_summary,
|
||||
@@ -1039,6 +1047,81 @@ def _build_decision_assistant_workspace_continuity_handoff(
|
||||
}
|
||||
|
||||
|
||||
def _build_decision_assistant_focus_workspace_context(
|
||||
*,
|
||||
top_opportunities: list[dict],
|
||||
candidate_watchlist_summary: dict,
|
||||
review_entrypoints: dict,
|
||||
digest_surface: dict,
|
||||
setup_learning_rollup: dict,
|
||||
) -> dict:
|
||||
watchlist_focus = dict(candidate_watchlist_summary.get("next_watchlist_focus") or {})
|
||||
top_opportunity = dict(top_opportunities[0] or {}) if top_opportunities else {}
|
||||
digest_action = dict(digest_surface.get("next_digest_action") or {})
|
||||
learning_focus = dict(setup_learning_rollup.get("next_learning_focus") or {})
|
||||
focus_title = str(
|
||||
watchlist_focus.get("title")
|
||||
or top_opportunity.get("title")
|
||||
or learning_focus.get("title")
|
||||
or "Current Focus"
|
||||
)
|
||||
focus_session_code = str(
|
||||
watchlist_focus.get("session_code")
|
||||
or learning_focus.get("target_session_code")
|
||||
or setup_learning_rollup.get("session_code")
|
||||
or "OFF_HOURS"
|
||||
)
|
||||
focus_detail = str(
|
||||
watchlist_focus.get("detail")
|
||||
or top_opportunity.get("detail")
|
||||
or learning_focus.get("prompt")
|
||||
or "Focus context unavailable."
|
||||
)
|
||||
workspace_sections = [
|
||||
{
|
||||
"section": "Watchlist",
|
||||
"target_section": "watchlist-review-now",
|
||||
"status": "active" if watchlist_focus else "idle",
|
||||
"prompt": str(candidate_watchlist_summary.get("next_watchlist_focus_prompt") or "Review the top watchlist candidate."),
|
||||
},
|
||||
{
|
||||
"section": "Evidence",
|
||||
"target_section": "evidence",
|
||||
"status": "active" if top_opportunity else "watch",
|
||||
"prompt": str(top_opportunity.get("reason") or "Review the supporting evidence for the current focus."),
|
||||
},
|
||||
{
|
||||
"section": "Review Entry",
|
||||
"target_section": "review-entry",
|
||||
"status": review_entrypoints.get("status") or "idle",
|
||||
"prompt": str(dict(review_entrypoints.get("next_review_entry_action") or {}).get("prompt") or review_entrypoints.get("next_review_action") or "Capture the next review action."),
|
||||
},
|
||||
{
|
||||
"section": "Digest",
|
||||
"target_section": "digest",
|
||||
"status": digest_surface.get("status") or "idle",
|
||||
"prompt": str(digest_action.get("prompt") or "Review the next digest action."),
|
||||
},
|
||||
{
|
||||
"section": "Replay",
|
||||
"target_section": "setup-replay",
|
||||
"status": setup_learning_rollup.get("status") or "idle",
|
||||
"prompt": str(setup_learning_rollup.get("next_learning_focus_prompt") or "Review replay learning for this focus."),
|
||||
},
|
||||
]
|
||||
return {
|
||||
"status": "active",
|
||||
"focus_title": focus_title,
|
||||
"focus_session_code": focus_session_code,
|
||||
"focus_candidate_id": watchlist_focus.get("candidate_id"),
|
||||
"focus_model_code": watchlist_focus.get("model_code"),
|
||||
"focus_tier": watchlist_focus.get("tier"),
|
||||
"focus_detail": focus_detail,
|
||||
"workspace_sections": workspace_sections,
|
||||
"next_focus_prompt": next((item.get("prompt") for item in workspace_sections if item.get("status") in {"active", "watch"}), None),
|
||||
}
|
||||
|
||||
|
||||
def _continuous_workspace_status(continuous_workspace_rail: list[dict]) -> str:
|
||||
if not continuous_workspace_rail:
|
||||
return "idle"
|
||||
|
||||
+78
-4
@@ -35,6 +35,7 @@ let autoRefreshTimer = null;
|
||||
let assistantHistoryKindFilter = loadAssistantHistoryKindFilter();
|
||||
let assistantDigestPreferenceMode = loadAssistantDigestPreferenceMode();
|
||||
let assistantScanHandoffMarker = loadAssistantScanHandoffMarker();
|
||||
let assistantFocusCandidateId = loadAssistantFocusCandidateId();
|
||||
|
||||
async function fetchJson(url, options = {}) {
|
||||
const fetchOptions = { ...options };
|
||||
@@ -139,6 +140,27 @@ function loadAssistantScanHandoffMarker() {
|
||||
}
|
||||
}
|
||||
|
||||
function loadAssistantFocusCandidateId() {
|
||||
try {
|
||||
return window.localStorage?.getItem('assistantFocusCandidateId') || '';
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function persistAssistantFocusCandidateId(value) {
|
||||
assistantFocusCandidateId = value == null ? '' : String(value);
|
||||
try {
|
||||
if (assistantFocusCandidateId) {
|
||||
window.localStorage?.setItem('assistantFocusCandidateId', assistantFocusCandidateId);
|
||||
} else {
|
||||
window.localStorage?.removeItem('assistantFocusCandidateId');
|
||||
}
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
function persistAssistantScanHandoffMarker(marker) {
|
||||
assistantScanHandoffMarker = marker || null;
|
||||
try {
|
||||
@@ -535,6 +557,7 @@ function renderDecisionAssistant(summary) {
|
||||
const digestHistoryBrowser = summary.digest_history_browser || digestSurface?.history_browser || null;
|
||||
const digestPreferenceProfile = digestSurface?.digest_preference_profile || null;
|
||||
const setupLearningRollup = summary.setup_learning_rollup || null;
|
||||
const focusWorkspaceContext = summary.focus_workspace_context || null;
|
||||
const continuousWorkspaceRail = summary.continuous_workspace_rail || [];
|
||||
const continuousWorkspaceSummary = summary.continuous_workspace_summary || null;
|
||||
const workspaceContinuityHandoff = summary.workspace_continuity_handoff || null;
|
||||
@@ -548,8 +571,19 @@ function renderDecisionAssistant(summary) {
|
||||
const visibleReminders = filterAssistantReminders(personalizedReminders, effectiveDigestPreferenceMode);
|
||||
const reminderCards = visibleReminders.length ? visibleReminders : personalizedReminders;
|
||||
const savedHandoffAge = resolveAssistantHandoffAge(assistantScanHandoffMarker, scanHandoffSummary?.default_stale_after_seconds);
|
||||
const allWatchlistItems = [
|
||||
...(watchlistTiers.review_now || []),
|
||||
...(watchlistTiers.strong_watch || []),
|
||||
...(watchlistTiers.soft_watch || []),
|
||||
...(watchlistTiers.archive_only || []),
|
||||
];
|
||||
const resolvedFocusCandidate = allWatchlistItems.find((item) => String(item.candidate_id || '') === String(assistantFocusCandidateId || ''))
|
||||
|| allWatchlistItems.find((item) => String(item.candidate_id || '') === String(focusWorkspaceContext?.focus_candidate_id || ''))
|
||||
|| allWatchlistItems[0]
|
||||
|| null;
|
||||
const assistantSections = [
|
||||
scanSummaryCards.length ? { title: 'Scan' } : null,
|
||||
focusWorkspaceContext ? { title: 'Focus Workspace' } : null,
|
||||
assistantLoopQueue.length ? { title: 'Loop Queue' } : null,
|
||||
continuousWorkspaceRail.length ? { title: 'Workspace Rail' } : null,
|
||||
workflowDigest.length ? { title: 'Workflow' } : null,
|
||||
@@ -751,6 +785,39 @@ function renderDecisionAssistant(summary) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (focusWorkspaceContext) {
|
||||
appendSectionTitle(decisionAssistantPanel, 'Focus Workspace');
|
||||
decisionAssistantPanel.appendChild(
|
||||
buildInfoGrid([
|
||||
['focus status', focusWorkspaceContext.status || 'idle'],
|
||||
['focus title', resolvedFocusCandidate?.title || focusWorkspaceContext.focus_title || 'n/a'],
|
||||
['focus session', resolvedFocusCandidate?.session_code || focusWorkspaceContext.focus_session_code || 'n/a'],
|
||||
['focus tier', resolvedFocusCandidate?.tier || focusWorkspaceContext.focus_tier || 'n/a'],
|
||||
['focus model', resolvedFocusCandidate?.model_code || focusWorkspaceContext.focus_model_code || 'n/a'],
|
||||
['focus detail', resolvedFocusCandidate?.detail || focusWorkspaceContext.focus_detail || 'n/a'],
|
||||
['next focus prompt', focusWorkspaceContext.next_focus_prompt || 'n/a'],
|
||||
])
|
||||
);
|
||||
const focusGrid = createElement('div', 'cards assistant-workspace-rail-grid');
|
||||
(focusWorkspaceContext.workspace_sections || []).forEach((item) => {
|
||||
const card = buildItemCard(
|
||||
item.section || 'section',
|
||||
item.status || 'idle',
|
||||
[
|
||||
['prompt', item.prompt || 'n/a'],
|
||||
['target', item.target_section || 'n/a'],
|
||||
],
|
||||
[{
|
||||
label: `Open ${item.section || 'Section'}`,
|
||||
onClick: () => revealAssistantSection(item.target_section || normalizeAssistantSectionId(item.section || '')),
|
||||
}]
|
||||
);
|
||||
card.classList.add('clickable-card');
|
||||
card.addEventListener('click', () => revealAssistantSection(item.target_section || normalizeAssistantSectionId(item.section || '')));
|
||||
focusGrid.appendChild(card);
|
||||
});
|
||||
decisionAssistantPanel.appendChild(focusGrid);
|
||||
}
|
||||
if (assistantLoopQueue.length) {
|
||||
appendSectionTitle(decisionAssistantPanel, 'Loop Queue');
|
||||
decisionAssistantPanel.appendChild(
|
||||
@@ -1509,8 +1576,7 @@ function renderDecisionAssistant(summary) {
|
||||
}
|
||||
const tierGrid = createElement('div', 'cards assistant-tight-grid');
|
||||
items.forEach((item) => {
|
||||
tierGrid.appendChild(
|
||||
buildItemCard(
|
||||
const card = buildItemCard(
|
||||
`${label}: ${item.title || 'candidate'}`,
|
||||
item.status || key,
|
||||
[
|
||||
@@ -1528,8 +1594,16 @@ function renderDecisionAssistant(summary) {
|
||||
onClick: () => runAction(item.action_path, {}),
|
||||
}]
|
||||
: []
|
||||
)
|
||||
);
|
||||
);
|
||||
card.classList.add('clickable-card');
|
||||
if (resolvedFocusCandidate && String(resolvedFocusCandidate.candidate_id || '') === String(item.candidate_id || '')) {
|
||||
card.classList.add('assistant-focus-card');
|
||||
}
|
||||
card.addEventListener('click', () => {
|
||||
persistAssistantFocusCandidateId(item.candidate_id);
|
||||
renderDecisionAssistant(window.__latestDecisionAssistantSummary || summary);
|
||||
});
|
||||
tierGrid.appendChild(card);
|
||||
});
|
||||
decisionAssistantPanel.appendChild(tierGrid);
|
||||
});
|
||||
|
||||
@@ -268,6 +268,10 @@ th { color: #9db0d3; }
|
||||
.card.clickable-card:hover {
|
||||
border-color: #4f7cff;
|
||||
}
|
||||
.assistant-focus-card {
|
||||
border-color: #4f7cff;
|
||||
box-shadow: inset 0 0 0 1px rgba(79, 124, 255, 0.3);
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
|
||||
@@ -193,6 +193,9 @@ class OperatorWorkspaceServiceTests(unittest.TestCase):
|
||||
self.assertEqual(summary["assistant_loop_summary"]["status"], "active")
|
||||
self.assertTrue(summary["assistant_loop_summary"]["next_target_section"])
|
||||
self.assertTrue(any(item["queue_id"] == "replay_learning" for item in summary["assistant_loop_queue"]))
|
||||
self.assertIn("focus_workspace_context", summary)
|
||||
self.assertEqual(summary["focus_workspace_context"]["status"], "active")
|
||||
self.assertTrue(summary["focus_workspace_context"]["workspace_sections"])
|
||||
self.assertIn("continuous_workspace_rail", summary)
|
||||
self.assertIn("continuous_workspace_status", summary)
|
||||
self.assertIn("continuous_workspace_summary", summary)
|
||||
|
||||
Reference in New Issue
Block a user