fix(ext): !stop CancelCascadeInvocation RPC — AG 빨간■ 동일 메커니즘 적용 #task-411
This commit is contained in:
@@ -43,12 +43,10 @@
|
|||||||
### [2026-03-11] rejectAgentStep — AG 미등록 VS Code 커맨드
|
### [2026-03-11] rejectAgentStep — AG 미등록 VS Code 커맨드
|
||||||
- **증상**: `/stop` 및 거부 시 `antigravity.agent.rejectAgentStep` → `command not found`
|
- **증상**: `/stop` 및 거부 시 `antigravity.agent.rejectAgentStep` → `command not found`
|
||||||
- **원인**: AG IDE가 이 커맨드를 런타임에 등록하지 않음 (상수 정의만 존재)
|
- **원인**: AG IDE가 이 커맨드를 런타임에 등록하지 않음 (상수 정의만 존재)
|
||||||
- **해결** (2026-03-18): `command-handler.ts`의 `!stop` 핸들러를 `sdk.cascade.cancelCurrentTask()`로 교체.
|
- **해결** (2026-03-18): `_cancelCurrentCascade()` 헬퍼 추가 — `sdk.titles.getActiveCascadeId()` → `ls.cancelCascade(cascadeId)` (CancelCascadeInvocation RPC).
|
||||||
WS 경로는 이미 SDK 사용 중이었으므로 file-based 경로만 수정.
|
AG의 빨간색 ■ 버튼과 동일한 메커니즘. rawRPC fallback 포함.
|
||||||
- `CancelCascadeInvocation` gRPC 메서드도 사용 가능 (cascade_id 필요)
|
- ~~`sdk.cascade.cancelCurrentTask()` — SDK에 존재하지 않는 메서드, 무시됨~~
|
||||||
- **E2E 검증 필요** — AG 가동 중 `!stop` 명령 테스트
|
- **주의**: `getActiveCascadeId()`가 null이면 취소 불가 — 로그로 확인 필요
|
||||||
- **주의**: `sdk.cascade.rejectStep()`은 여전히 내부적으로 `rejectAgentStep` 커맨드를 호출할 수 있음.
|
|
||||||
단일 step 거부보다 `cancelCurrentTask()`(전체 중단)가 더 안정적.
|
|
||||||
- **Vikunja**: #411
|
- **Vikunja**: #411
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -4,4 +4,5 @@
|
|||||||
|---|------|------|------|------|
|
|---|------|------|------|------|
|
||||||
| 001 | 06:09~06:26 | known-issues 정리/아카이빙 + Collector 폐기 마킹 + 문서 보강 (architecture, tech-stack, conventions) | `881a424` | ✅ |
|
| 001 | 06:09~06:26 | known-issues 정리/아카이빙 + Collector 폐기 마킹 + 문서 보강 (architecture, tech-stack, conventions) | `881a424` | ✅ |
|
||||||
| 002 | 06:33~06:50 | Hub/WS 단위 테스트 45개 작성 (연결 관리, pending_owners, 라우팅, 인증) | `ac803d4` | ✅ |
|
| 002 | 06:33~06:50 | Hub/WS 단위 테스트 45개 작성 (연결 관리, pending_owners, 라우팅, 인증) | `ac803d4` | ✅ |
|
||||||
| 003 | 06:50~07:00 | rejectAgentStep 대안 조사 — CancelCascadeInvocation RPC 발견 | `bcca706` | 🔧 |
|
| 003 | 06:50~07:05 | !stop 핸들러 SDK cancelCurrentTask() 교체 + VSIX 재빌드/설치 | `759dab5` | ✅ |
|
||||||
|
| 004 | 07:03~07:15 | !stop 재수정 — cancelCurrentTask→CancelCascadeInvocation RPC (AG 빨간■ 동일) + VSIX 설치 | `8d5940b` | ✅ |
|
||||||
|
|||||||
@@ -435,7 +435,7 @@ async function activate(context) {
|
|||||||
onCommand: (data) => {
|
onCommand: (data) => {
|
||||||
logToFile(`[WS-CMD] ${data.text?.substring(0, 50)}`);
|
logToFile(`[WS-CMD] ${data.text?.substring(0, 50)}`);
|
||||||
(0, command_handler_1.handleWSCommand)({
|
(0, command_handler_1.handleWSCommand)({
|
||||||
bridgePath, projectName, sdk, autoApproveEnabled, logToFile,
|
bridgePath, projectName, sdk, ls: sdk?.ls, autoApproveEnabled, logToFile,
|
||||||
onAutoApproveChanged: (enabled) => { autoApproveEnabled = enabled; },
|
onAutoApproveChanged: (enabled) => { autoApproveEnabled = enabled; },
|
||||||
recentDiscordSentTexts,
|
recentDiscordSentTexts,
|
||||||
}, data);
|
}, data);
|
||||||
@@ -558,7 +558,7 @@ async function activate(context) {
|
|||||||
}
|
}
|
||||||
// Watch commands directory
|
// Watch commands directory
|
||||||
(0, command_handler_1.watchCommandsDir)({
|
(0, command_handler_1.watchCommandsDir)({
|
||||||
bridgePath, projectName, sdk, autoApproveEnabled, logToFile,
|
bridgePath, projectName, sdk, ls: sdk?.ls, autoApproveEnabled, logToFile,
|
||||||
onAutoApproveChanged: (enabled) => { autoApproveEnabled = enabled; },
|
onAutoApproveChanged: (enabled) => { autoApproveEnabled = enabled; },
|
||||||
recentDiscordSentTexts,
|
recentDiscordSentTexts,
|
||||||
});
|
});
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -17,6 +17,8 @@ export interface CommandHandlerContext {
|
|||||||
bridgePath: string;
|
bridgePath: string;
|
||||||
projectName: string;
|
projectName: string;
|
||||||
sdk: any;
|
sdk: any;
|
||||||
|
/** LSBridge instance for direct LS RPC calls (cancelCascade, etc.) */
|
||||||
|
ls: any;
|
||||||
autoApproveEnabled: boolean;
|
autoApproveEnabled: boolean;
|
||||||
logToFile: (msg: string) => void;
|
logToFile: (msg: string) => void;
|
||||||
/** Called when auto-approve is toggled; extension.ts updates its own state */
|
/** Called when auto-approve is toggled; extension.ts updates its own state */
|
||||||
@@ -93,9 +95,7 @@ export function handleWSCommand(ctx: CommandHandlerContext, data: { text?: strin
|
|||||||
|
|
||||||
if (text === '!stop') {
|
if (text === '!stop') {
|
||||||
ctx.logToFile('[WS-CMD] !stop — cancelling AG task');
|
ctx.logToFile('[WS-CMD] !stop — cancelling AG task');
|
||||||
if (ctx.sdk) {
|
_cancelCurrentCascade(ctx);
|
||||||
try { ctx.sdk.cascade.cancelCurrentTask(); } catch { }
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +122,42 @@ export function handleWSCommand(ctx: CommandHandlerContext, data: { text?: strin
|
|||||||
|
|
||||||
// ─── Private ───
|
// ─── Private ───
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel the currently active cascade via CancelCascadeInvocation RPC.
|
||||||
|
* This is the same mechanism AG's native red ■ stop button uses.
|
||||||
|
*/
|
||||||
|
async function _cancelCurrentCascade(ctx: CommandHandlerContext) {
|
||||||
|
// 1. Get the active cascade ID from SDK titles manager
|
||||||
|
const cascadeId = ctx.sdk?.titles?.getActiveCascadeId?.();
|
||||||
|
if (!cascadeId) {
|
||||||
|
ctx.logToFile('[STOP] No active cascade to cancel (getActiveCascadeId returned null)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.logToFile(`[STOP] Cancelling cascade: ${cascadeId.substring(0, 12)}...`);
|
||||||
|
|
||||||
|
// 2. Use LSBridge.cancelCascade() → CancelCascadeInvocation RPC
|
||||||
|
if (ctx.ls) {
|
||||||
|
try {
|
||||||
|
await ctx.ls.cancelCascade(cascadeId);
|
||||||
|
ctx.logToFile(`[STOP] ✅ CancelCascadeInvocation sent for ${cascadeId.substring(0, 12)}`);
|
||||||
|
return;
|
||||||
|
} catch (e: any) {
|
||||||
|
ctx.logToFile(`[STOP] LSBridge cancelCascade failed: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Fallback: try rawRPC directly via sdk.ls
|
||||||
|
if (ctx.sdk?.ls?.rawRPC) {
|
||||||
|
try {
|
||||||
|
await ctx.sdk.ls.rawRPC('CancelCascadeInvocation', { cascadeId });
|
||||||
|
ctx.logToFile(`[STOP] ✅ rawRPC CancelCascadeInvocation sent for ${cascadeId.substring(0, 12)}`);
|
||||||
|
} catch (e: any) {
|
||||||
|
ctx.logToFile(`[STOP] rawRPC fallback also failed: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function _processCommandFile(filePath: string, ctx: CommandHandlerContext) {
|
function _processCommandFile(filePath: string, ctx: CommandHandlerContext) {
|
||||||
try {
|
try {
|
||||||
const content = fs.readFileSync(filePath, 'utf-8');
|
const content = fs.readFileSync(filePath, 'utf-8');
|
||||||
@@ -158,15 +194,8 @@ function _processCommandFile(filePath: string, ctx: CommandHandlerContext) {
|
|||||||
console.log(`Gravity Bridge: approve_terminal error: ${e.message}`)
|
console.log(`Gravity Bridge: approve_terminal error: ${e.message}`)
|
||||||
);
|
);
|
||||||
} else if (text === '!stop') {
|
} else if (text === '!stop') {
|
||||||
// Cancel current operation — use SDK (rejectAgentStep is NOT a registered VS Code command)
|
// Cancel current operation — use CancelCascadeInvocation RPC (same as AG's red ■ button)
|
||||||
if (ctx.sdk) {
|
_cancelCurrentCascade(ctx);
|
||||||
try {
|
|
||||||
ctx.sdk.cascade.cancelCurrentTask();
|
|
||||||
console.log('Gravity Bridge: ✅ stop sent via SDK');
|
|
||||||
} catch (e: any) {
|
|
||||||
console.log(`Gravity Bridge: stop error: ${e.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (text.startsWith('!auto')) {
|
} else if (text.startsWith('!auto')) {
|
||||||
// Auto-approve mode toggle
|
// Auto-approve mode toggle
|
||||||
let enabled: boolean;
|
let enabled: boolean;
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ function writeChatSnapshot(text: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeChatSnapshotWithFiles(text: string, files: Array<{name: string, content: string}>) {
|
function writeChatSnapshotWithFiles(text: string, files: Array<{ name: string, content: string }>) {
|
||||||
try {
|
try {
|
||||||
// WS route (preferred) — skip file write to prevent duplicate Discord delivery
|
// WS route (preferred) — skip file write to prevent duplicate Discord delivery
|
||||||
if (wsBridge && wsBridge.isConnected()) {
|
if (wsBridge && wsBridge.isConnected()) {
|
||||||
@@ -366,6 +366,7 @@ let sawRunningAfterPending = true;
|
|||||||
// ─── Step Probe, Response Watcher, Approval Strategies → extracted to ./step-probe.ts ───
|
// ─── Step Probe, Response Watcher, Approval Strategies → extracted to ./step-probe.ts ───
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function activate(context: vscode.ExtensionContext) {
|
export async function activate(context: vscode.ExtensionContext) {
|
||||||
console.log('Gravity Bridge: activating...');
|
console.log('Gravity Bridge: activating...');
|
||||||
|
|
||||||
@@ -426,7 +427,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||||||
onCommand: (data: WSCommandData) => {
|
onCommand: (data: WSCommandData) => {
|
||||||
logToFile(`[WS-CMD] ${data.text?.substring(0, 50)}`);
|
logToFile(`[WS-CMD] ${data.text?.substring(0, 50)}`);
|
||||||
handleWSCommand({
|
handleWSCommand({
|
||||||
handleWSCommand({
|
bridgePath, projectName, sdk, ls: sdk?.ls, autoApproveEnabled, logToFile,
|
||||||
onAutoApproveChanged: (enabled: boolean) => { autoApproveEnabled = enabled; },
|
onAutoApproveChanged: (enabled: boolean) => { autoApproveEnabled = enabled; },
|
||||||
recentDiscordSentTexts,
|
recentDiscordSentTexts,
|
||||||
}, data);
|
}, data);
|
||||||
@@ -555,7 +556,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||||||
|
|
||||||
// Watch commands directory
|
// Watch commands directory
|
||||||
watchCommandsDir({
|
watchCommandsDir({
|
||||||
watchCommandsDir({
|
bridgePath, projectName, sdk, ls: sdk?.ls, autoApproveEnabled, logToFile,
|
||||||
onAutoApproveChanged: (enabled: boolean) => { autoApproveEnabled = enabled; },
|
onAutoApproveChanged: (enabled: boolean) => { autoApproveEnabled = enabled; },
|
||||||
recentDiscordSentTexts,
|
recentDiscordSentTexts,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user