feat(agent): support Responses steering and project-aware tools

Use the dedicated TJWATER_AGENT_URL and Responses stream protocol. Keep running
turns active while users steer or queue follow-ups and edit pending delivery.
Refresh the model catalog before generation and preserve approval mode selection.

Integrate scoped investigation messaging/presentation, geographic place selection,
current Agent composer styles and supporting workbench contracts.

Validation: 77 focused Vitest checks, 21 browser cases, lint, typecheck and
production build. Preserve direct Server/Agent routing and project authentication.
This commit is contained in:
2026-10-19 18:46:58 +08:00
parent 2c5bf15cce
commit ec23d44b02
131 changed files with 6663 additions and 1204 deletions
@@ -94,6 +94,7 @@ type ScheduledConditionFeedProps = {
onLoadTaskEvidenceSnapshot?: (taskId: string, evidenceId: string) => Promise<OperationalTaskDataSnapshot>;
onTaskRefresh?: () => void;
onLoadTaskReports?: (taskId: string, offset: number) => Promise<OperationalTaskReportPage>;
onLocate?: (targets: import("../api/investigation-report").InvestigationMapTarget[]) => void;
onReportViewed?: (taskId: string, generation: number, revision: number) => void;
onRefresh?: () => void | Promise<void>;
};
@@ -133,6 +134,7 @@ export function ScheduledConditionFeed({
onLoadTaskEvidenceSnapshot,
onTaskRefresh,
onLoadTaskReports,
onLocate,
onReportViewed,
onRefresh
}: ScheduledConditionFeedProps) {
@@ -140,10 +142,14 @@ export function ScheduledConditionFeed({
const [uncontrolledSelectedConditionId, setUncontrolledSelectedConditionId] = useState<
string | null
>(null);
const [retainedScheduledCondition, setRetainedScheduledCondition] = useState<ScheduledConditionItem | null>(null);
const [taskFilter, setTaskFilter] = useState<ConditionTaskFilterValue>(CONDITION_FILTER_ALL);
const [statusFilter, setStatusFilter] =
useState<ConditionStatusFilterValue>(CONDITION_FILTER_ALL);
const [taskCategory, setTaskCategory] = useState<WorkbenchTaskCategory>("scheduled");
const [taskCategory, setTaskCategory] = useState<WorkbenchTaskCategory>(() => {
const selected = conditions.find((condition) => condition.id === controlledSelectedConditionId);
return selected ? getWorkbenchTaskCategory(selected) ?? "scheduled" : "scheduled";
});
const [refreshing, setRefreshing] = useState(false);
const refreshingRef = useRef(false);
const mobileSheet = presentation === "mobile-sheet";
@@ -156,7 +162,14 @@ export function ScheduledConditionFeed({
() => groupConditionsByTaskCategory(conditions),
[conditions]
);
const categoryConditions = conditionsByCategory[taskCategory];
const categoryConditions = useMemo(() => {
const current = conditionsByCategory[taskCategory];
return taskCategory === "scheduled"
&& retainedScheduledCondition?.id === selectedConditionId
&& !current.some((condition) => condition.id === selectedConditionId)
? [...current, retainedScheduledCondition]
: current;
}, [conditionsByCategory, retainedScheduledCondition, selectedConditionId, taskCategory]);
const activeLoading = taskCategory === "scheduled" ? loading : backendLoading;
const { recentConditions: allRecentConditions, futureWorkOrders: allFutureWorkOrders } = useMemo(
() => groupScheduledConditions(categoryConditions),
@@ -244,11 +257,18 @@ export function ScheduledConditionFeed({
return;
}
// A controlled task selection survives panel remounts and detail-loading windows.
const selected = conditions.find((condition) => condition.id === controlledSelectedConditionId);
if (selected) {
const category = getWorkbenchTaskCategory(selected);
if (category && category !== taskCategory) { setTaskCategory(category); return; }
} else if (controlledSelectedConditionId && (loading || backendLoading)) return;
const nextSelectedConditionId = selectedCondition?.id ?? null;
if (selectedConditionId !== nextSelectedConditionId) {
updateSelectedConditionId(nextSelectedConditionId);
}
}, [expanded, selectedCondition?.id, selectedConditionId, updateSelectedConditionId]);
}, [backendLoading, conditions, controlledSelectedConditionId, expanded, loading, selectedCondition?.id, selectedConditionId, taskCategory, updateSelectedConditionId]);
useEffect(() => {
if (!focusConditionId || focusRequestId === undefined) {
@@ -268,6 +288,7 @@ export function ScheduledConditionFeed({
setTaskCategory(targetCategory);
setTaskFilter(CONDITION_FILTER_ALL);
setStatusFilter(CONDITION_FILTER_ALL);
setRetainedScheduledCondition(targetCategory === "scheduled" ? targetCondition : null);
updateSelectedConditionId(targetCondition.id);
setFocusedAction(focusRequest?.actionId ? { conditionId: targetCondition.id, actionId: focusRequest.actionId } : null);
updateExpanded(true);
@@ -293,6 +314,7 @@ export function ScheduledConditionFeed({
setTaskCategory(nextCategory);
setTaskFilter(CONDITION_FILTER_ALL);
setStatusFilter(CONDITION_FILTER_ALL);
setRetainedScheduledCondition(null);
updateSelectedConditionId(
sortConditionsNewestFirst(conditionsByCategory[nextCategory])[0]?.id ?? null
);
@@ -329,6 +351,11 @@ export function ScheduledConditionFeed({
}
function handleSelectTimelineCondition(conditionId: string) {
if (taskCategory === "scheduled") {
setRetainedScheduledCondition(
categoryConditions.find((condition) => condition.id === conditionId) ?? null
);
}
updateSelectedConditionId(conditionId);
if (!expanded) {
updateExpanded(true);
@@ -521,6 +548,7 @@ export function ScheduledConditionFeed({
<ConditionDetailSkeleton />
) : expanded && selectedCondition ? (
<ConditionDetailPanel
onLocate={onLocate}
condition={selectedCondition}
dataSource={dataSource}
workOrderDataSource={workOrderDataSource}