fix: suppress inherited subagent notifications
Subagent completion notifications respect notification settings Permission auto-accept now suppresses notifications for known child sessions Server notification checks use OpenCode session parent semantics
This commit is contained in:
@@ -169,20 +169,24 @@ export const usePermissionStore = create<PermissionStore>()(
|
|||||||
return { autoAccept };
|
return { autoAccept };
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mirror state to the server so it can suppress permission
|
const sessionScope = resolveSessionScope(sessionId, sessions);
|
||||||
// notifications at the source (otherwise the 500ms debounce
|
|
||||||
// races with the client's auto-response and can leak).
|
// Mirror inherited state to the server so it can suppress
|
||||||
void fetch('/api/notifications/auto-accept', {
|
// permission notifications before the client auto-response
|
||||||
method: 'POST',
|
// round-trip. Send known descendants too; server-side
|
||||||
headers: { 'Content-Type': 'application/json' },
|
// ancestry lookup can lag OpenCode session indexing.
|
||||||
body: JSON.stringify({ sessionId, enabled }),
|
for (const scopedSessionId of sessionScope) {
|
||||||
}).catch(() => { /* best-effort */ });
|
void fetch('/api/notifications/auto-accept', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ sessionId: scopedSessionId, enabled }),
|
||||||
|
}).catch(() => { /* best-effort */ });
|
||||||
|
}
|
||||||
|
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionScope = resolveSessionScope(sessionId, sessions);
|
|
||||||
const sessionDirectory = useSessionUIStore.getState().getDirectoryForSession(sessionId);
|
const sessionDirectory = useSessionUIStore.getState().getDirectoryForSession(sessionId);
|
||||||
const directories = new Set<string>();
|
const directories = new Set<string>();
|
||||||
const currentDirectory = normalizeDirectoryCandidate(opencodeClient.getDirectory());
|
const currentDirectory = normalizeDirectoryCandidate(opencodeClient.getDirectory());
|
||||||
|
|||||||
@@ -60,9 +60,26 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const setCachedSessionParentId = (sessionId, parentID) => {
|
const setCachedSessionParentId = (sessionId, parentID) => {
|
||||||
|
if (!parentID) return;
|
||||||
sessionParentIdCache.set(sessionId, { parentID: parentID ?? null, at: Date.now() });
|
sessionParentIdCache.set(sessionId, { parentID: parentID ?? null, at: Date.now() });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getParentIdFromPayload = (payload) => {
|
||||||
|
if (!payload || typeof payload !== 'object') return null;
|
||||||
|
if (payload.type !== 'session.created' && payload.type !== 'session.updated') return null;
|
||||||
|
const parentID = payload.properties?.info?.parentID ?? null;
|
||||||
|
return typeof parentID === 'string' && parentID.length > 0 ? parentID : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const maybeCacheSessionParentFromPayload = (payload) => {
|
||||||
|
const sessionId = extractSessionIdFromPayload(payload);
|
||||||
|
if (typeof sessionId !== 'string' || sessionId.length === 0) return;
|
||||||
|
const parentID = getParentIdFromPayload(payload);
|
||||||
|
if (parentID) {
|
||||||
|
setCachedSessionParentId(sessionId, parentID);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const fetchSessionParentId = async (sessionId) => {
|
const fetchSessionParentId = async (sessionId) => {
|
||||||
if (!sessionId) return undefined;
|
if (!sessionId) return undefined;
|
||||||
|
|
||||||
@@ -82,12 +99,19 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const data = await response.json().catch(() => null);
|
const data = await response.json().catch(() => null);
|
||||||
if (!Array.isArray(data)) {
|
const sessions = Array.isArray(data)
|
||||||
|
? data
|
||||||
|
: Array.isArray(data?.items)
|
||||||
|
? data.items
|
||||||
|
: Array.isArray(data?.data)
|
||||||
|
? data.data
|
||||||
|
: null;
|
||||||
|
if (!sessions) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const match = data.find((session) => session && typeof session === 'object' && session.id === sessionId);
|
const match = sessions.find((session) => session && typeof session === 'object' && session.id === sessionId);
|
||||||
const parentID = match?.parentID ? match.parentID : null;
|
const parentID = match?.parentID ?? null;
|
||||||
setCachedSessionParentId(sessionId, parentID);
|
setCachedSessionParentId(sessionId, parentID);
|
||||||
return parentID;
|
return parentID;
|
||||||
} catch {
|
} catch {
|
||||||
@@ -164,6 +188,8 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maybeCacheSessionParentFromPayload(payload);
|
||||||
|
|
||||||
const sessionId = extractSessionIdFromPayload(payload);
|
const sessionId = extractSessionIdFromPayload(payload);
|
||||||
if (payload.type === 'message.updated') {
|
if (payload.type === 'message.updated') {
|
||||||
const info = payload.properties?.info;
|
const info = payload.properties?.info;
|
||||||
@@ -171,8 +197,7 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|||||||
const settings = await readSettingsFromDisk();
|
const settings = await readSettingsFromDisk();
|
||||||
|
|
||||||
if (settings.notifyOnSubtasks === false) {
|
if (settings.notifyOnSubtasks === false) {
|
||||||
const sessionInfo = payload.properties?.session;
|
const parentIDFromPayload = getParentIdFromPayload(payload);
|
||||||
const parentIDFromPayload = sessionInfo?.parentID ?? payload.properties?.parentID;
|
|
||||||
const parentID = parentIDFromPayload
|
const parentID = parentIDFromPayload
|
||||||
? parentIDFromPayload
|
? parentIDFromPayload
|
||||||
: await fetchSessionParentId(sessionId);
|
: await fetchSessionParentId(sessionId);
|
||||||
@@ -437,6 +462,11 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|||||||
const timer = setTimeout(async () => {
|
const timer = setTimeout(async () => {
|
||||||
pushPermissionDebounceTimers.delete(sessionId);
|
pushPermissionDebounceTimers.delete(sessionId);
|
||||||
|
|
||||||
|
if (await isSessionAutoAccepting(sessionId)) {
|
||||||
|
if (requestKey) notifiedPermissionRequests.add(requestKey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const settings = await readSettingsFromDisk();
|
const settings = await readSettingsFromDisk();
|
||||||
|
|
||||||
if (settings.notifyOnQuestion === false) {
|
if (settings.notifyOnQuestion === false) {
|
||||||
|
|||||||
Reference in New Issue
Block a user