feat: capture AI text responses on RUNNING->IDLE for Discord relay
This commit is contained in:
@@ -1416,6 +1416,9 @@ function setupMonitor() {
|
|||||||
let lastModTime = ''; // track lastModifiedTime to distinguish thinking vs approval
|
let lastModTime = ''; // track lastModifiedTime to distinguish thinking vs approval
|
||||||
let stallProbed = false; // prevent repeated step probes during same stall
|
let stallProbed = false; // prevent repeated step probes during same stall
|
||||||
let lastRelayedTaskText = ''; // dedup TASK_BOUNDARY relay
|
let lastRelayedTaskText = ''; // dedup TASK_BOUNDARY relay
|
||||||
|
let wasRunning = false; // track RUNNING→IDLE transition for response capture
|
||||||
|
let lastUserInputStepIdx = -1; // track user input for response matching
|
||||||
|
let lastResponseCaptureStep = -1; // dedup: don't capture same response twice
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
pollCount++;
|
pollCount++;
|
||||||
if (pollCount <= 3 || pollCount % 12 === 0) {
|
if (pollCount <= 3 || pollCount % 12 === 0) {
|
||||||
@@ -1782,6 +1785,59 @@ function setupMonitor() {
|
|||||||
else if (pollCount <= 5) {
|
else if (pollCount <= 5) {
|
||||||
logToFile(`[TASK-STEP] null (no task step in session)`);
|
logToFile(`[TASK-STEP] null (no task step in session)`);
|
||||||
}
|
}
|
||||||
|
// ── RUNNING → IDLE transition: capture AI response for Discord ──
|
||||||
|
const userInputIdx = bestSession.lastUserInputStepIndex ?? -1;
|
||||||
|
if (userInputIdx > lastUserInputStepIdx) {
|
||||||
|
lastUserInputStepIdx = userInputIdx;
|
||||||
|
logToFile(`[RESPONSE-CAPTURE] user input detected at step ${userInputIdx}`);
|
||||||
|
}
|
||||||
|
if (wasRunning && !isRunning && currentCount > lastResponseCaptureStep) {
|
||||||
|
logToFile(`[RESPONSE-CAPTURE] RUNNING→IDLE, steps=${currentCount}, capturing response...`);
|
||||||
|
lastResponseCaptureStep = currentCount;
|
||||||
|
try {
|
||||||
|
const offset = Math.max(0, currentCount - 5);
|
||||||
|
const latestResp = await sdk.ls.rawRPC('GetCascadeTrajectorySteps', {
|
||||||
|
cascadeId: bestSessionId,
|
||||||
|
stepOffset: offset,
|
||||||
|
});
|
||||||
|
if (latestResp?.steps?.length > 0) {
|
||||||
|
const steps = latestResp.steps;
|
||||||
|
for (let ri = steps.length - 1; ri >= 0; ri--) {
|
||||||
|
const s = steps[ri];
|
||||||
|
const sType = s?.type || '';
|
||||||
|
if (sType.includes('PLANNER_RESPONSE') || sType.includes('MESSAGE')) {
|
||||||
|
const parts = s?.content?.parts || s?.parts || [];
|
||||||
|
let textContent = '';
|
||||||
|
for (const p of parts) {
|
||||||
|
if (p?.text)
|
||||||
|
textContent += p.text;
|
||||||
|
else if (typeof p === 'string')
|
||||||
|
textContent += p;
|
||||||
|
}
|
||||||
|
if (!textContent && s?.metadata?.text)
|
||||||
|
textContent = s.metadata.text;
|
||||||
|
if (!textContent && s?.rawOutput)
|
||||||
|
textContent = typeof s.rawOutput === 'string' ? s.rawOutput : JSON.stringify(s.rawOutput);
|
||||||
|
if (textContent.length > 10) {
|
||||||
|
logToFile(`[RESPONSE-CAPTURE] found ${sType} (${textContent.length} chars) at step ${offset + ri}`);
|
||||||
|
const truncated = textContent.length > 1800
|
||||||
|
? textContent.substring(0, 1800) + '\n\n_(이하 생략)_'
|
||||||
|
: textContent;
|
||||||
|
writeChatSnapshot(`💬 **AI 응답**\n\n${truncated}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logToFile(`[RESPONSE-CAPTURE] ${sType} too short (${textContent.length}), keys=[${Object.keys(s).join(',')}]`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (re) {
|
||||||
|
logToFile(`[RESPONSE-CAPTURE] error: ${re.message.substring(0, 100)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wasRunning = isRunning;
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
if (pollCount <= 5 || pollCount % 20 === 0) {
|
if (pollCount <= 5 || pollCount % 20 === 0) {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1391,6 +1391,9 @@ function setupMonitor() {
|
|||||||
let lastModTime = ''; // track lastModifiedTime to distinguish thinking vs approval
|
let lastModTime = ''; // track lastModifiedTime to distinguish thinking vs approval
|
||||||
let stallProbed = false; // prevent repeated step probes during same stall
|
let stallProbed = false; // prevent repeated step probes during same stall
|
||||||
let lastRelayedTaskText = ''; // dedup TASK_BOUNDARY relay
|
let lastRelayedTaskText = ''; // dedup TASK_BOUNDARY relay
|
||||||
|
let wasRunning = false; // track RUNNING→IDLE transition for response capture
|
||||||
|
let lastUserInputStepIdx = -1; // track user input for response matching
|
||||||
|
let lastResponseCaptureStep = -1; // dedup: don't capture same response twice
|
||||||
|
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
pollCount++;
|
pollCount++;
|
||||||
@@ -1748,6 +1751,55 @@ function setupMonitor() {
|
|||||||
} else if (pollCount <= 5) {
|
} else if (pollCount <= 5) {
|
||||||
logToFile(`[TASK-STEP] null (no task step in session)`);
|
logToFile(`[TASK-STEP] null (no task step in session)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── RUNNING → IDLE transition: capture AI response for Discord ──
|
||||||
|
const userInputIdx = bestSession.lastUserInputStepIndex ?? -1;
|
||||||
|
if (userInputIdx > lastUserInputStepIdx) {
|
||||||
|
lastUserInputStepIdx = userInputIdx;
|
||||||
|
logToFile(`[RESPONSE-CAPTURE] user input detected at step ${userInputIdx}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wasRunning && !isRunning && currentCount > lastResponseCaptureStep) {
|
||||||
|
logToFile(`[RESPONSE-CAPTURE] RUNNING→IDLE, steps=${currentCount}, capturing response...`);
|
||||||
|
lastResponseCaptureStep = currentCount;
|
||||||
|
try {
|
||||||
|
const offset = Math.max(0, currentCount - 5);
|
||||||
|
const latestResp = await sdk.ls.rawRPC('GetCascadeTrajectorySteps', {
|
||||||
|
cascadeId: bestSessionId,
|
||||||
|
stepOffset: offset,
|
||||||
|
});
|
||||||
|
if (latestResp?.steps?.length > 0) {
|
||||||
|
const steps = latestResp.steps;
|
||||||
|
for (let ri = steps.length - 1; ri >= 0; ri--) {
|
||||||
|
const s = steps[ri];
|
||||||
|
const sType = s?.type || '';
|
||||||
|
if (sType.includes('PLANNER_RESPONSE') || sType.includes('MESSAGE')) {
|
||||||
|
const parts = s?.content?.parts || s?.parts || [];
|
||||||
|
let textContent = '';
|
||||||
|
for (const p of parts) {
|
||||||
|
if (p?.text) textContent += p.text;
|
||||||
|
else if (typeof p === 'string') textContent += p;
|
||||||
|
}
|
||||||
|
if (!textContent && s?.metadata?.text) textContent = s.metadata.text;
|
||||||
|
if (!textContent && s?.rawOutput) textContent = typeof s.rawOutput === 'string' ? s.rawOutput : JSON.stringify(s.rawOutput);
|
||||||
|
if (textContent.length > 10) {
|
||||||
|
logToFile(`[RESPONSE-CAPTURE] found ${sType} (${textContent.length} chars) at step ${offset + ri}`);
|
||||||
|
const truncated = textContent.length > 1800
|
||||||
|
? textContent.substring(0, 1800) + '\n\n_(이하 생략)_'
|
||||||
|
: textContent;
|
||||||
|
writeChatSnapshot(`💬 **AI 응답**\n\n${truncated}`);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
logToFile(`[RESPONSE-CAPTURE] ${sType} too short (${textContent.length}), keys=[${Object.keys(s).join(',')}]`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (re: any) {
|
||||||
|
logToFile(`[RESPONSE-CAPTURE] error: ${re.message.substring(0, 100)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wasRunning = isRunning;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (pollCount <= 5 || pollCount % 20 === 0) {
|
if (pollCount <= 5 || pollCount % 20 === 0) {
|
||||||
console.log(`Gravity Bridge: [POLL#${pollCount}] error: ${e.message}`);
|
console.log(`Gravity Bridge: [POLL#${pollCount}] error: ${e.message}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user