Fix calendar routing and user-local time context (#408)

* fix(chat): add user-local time context

* fix(chat): route calendar follow-up phrasing

* refactor(chat): log tool intent routing reasons

* test(chat): align user time prompt shim

---------

Co-authored-by: Alex Kenley <Alex.Kenley@threatvectorsecurity.com>
This commit is contained in:
Alexander Kenley
2026-06-04 22:20:04 +10:00
committed by GitHub
parent f59edee611
commit 7b45a94b6d
12 changed files with 463 additions and 106 deletions

View File

@@ -1876,11 +1876,12 @@ function _wireAll(body) {
}
try {
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone || '';
const tzOffset = -new Date().getTimezoneOffset();
const res = await fetch(`${API_BASE}/api/calendar/quick-parse`, {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text, tz }),
body: JSON.stringify({ text, tz, tz_offset: tzOffset }),
});
const data = await res.json().catch(() => ({}));
if (!res.ok || !data.ok) {

View File

@@ -530,6 +530,9 @@ import createResearchSynapse from './researchSynapse.js';
let _renderStream = () => {};
let _cancelThinkingTimer = () => {};
let _removeThinkingSpinner = () => {};
let timeoutId = null;
let responseTimeoutCleared = false;
let clearResponseTimeout = () => {};
const clearProcessingProbe = () => {
if (processingProbeTimer) {
clearTimeout(processingProbeTimer);
@@ -790,13 +793,26 @@ import createResearchSynapse from './researchSynapse.js';
// Timeout: 6 min for research and agent mode, 3 min otherwise
const timeoutMs = el('research-toggle').checked || _isAgent ? RESEARCH_TIMEOUT_MS : DEFAULT_TIMEOUT_MS;
const timeoutId = setTimeout(() => {
timeoutId = setTimeout(() => {
if (!abortCtrl.signal.aborted) {
timedOut = true;
abortCtrl._reason = 'timeout';
try {
if (streamSessionId) {
fetch(`/api/chat/stop/${encodeURIComponent(streamSessionId)}`, {
method: 'POST',
credentials: 'same-origin',
}).catch(() => {});
}
} catch (_) {}
abortCtrl.abort();
}
}, timeoutMs);
clearResponseTimeout = () => {
if (responseTimeoutCleared) return;
responseTimeoutCleared = true;
clearTimeout(timeoutId);
};
const box = el('chat-history');
holder = document.createElement('div');
@@ -922,16 +938,19 @@ import createResearchSynapse from './researchSynapse.js';
// the agent so natural-language times like "today at 9pm" are
// interpreted in YOUR timezone, not the server's.
const _tzOffsetMin = -new Date().getTimezoneOffset();
const _tzName = (() => {
try { return Intl.DateTimeFormat().resolvedOptions().timeZone || ''; }
catch { return ''; }
})();
const res = await fetch(`${API_BASE}/api/chat_stream`, {
method: 'POST',
body: fd,
headers: { 'X-Tz-Offset': String(_tzOffsetMin) },
headers: { 'X-Tz-Offset': String(_tzOffsetMin), 'X-Tz-Name': _tzName },
signal: abortCtrl.signal
});
clearTimeout(timeoutId);
if (!res.ok) {
clearResponseTimeout();
if (res.status === 404) {
// Session was deleted (e.g. by AI) — reload and go to welcome
holder.remove();
@@ -1359,7 +1378,8 @@ import createResearchSynapse from './researchSynapse.js';
typewriterInto(roundHolder.querySelector('.body'), errMsg);
break;
}
if (json.delta || json.type === 'tool_start' || json.type === 'agent_step' || json.type === 'doc_stream_delta') {
if (json.delta || json.type === 'tool_start' || json.type === 'tool_output' || json.type === 'tool_progress' || json.type === 'agent_step' || json.type === 'doc_stream_open' || json.type === 'doc_stream_delta' || json.type === 'research_progress') {
clearResponseTimeout();
clearProcessingProbe();
}
if (json.delta) {
@@ -2710,6 +2730,7 @@ import createResearchSynapse from './researchSynapse.js';
}
}
} finally {
clearResponseTimeout();
clearProcessingProbe();
// Streaming done — let screen readers announce the settled response.
const _chatLogDone = document.getElementById('chat-history');