feat: Implement Pipeline Templates for Company Suite and refine orchestration logic
This commit is contained in:
@@ -45,6 +45,7 @@ import {
|
||||
ROLE_CATEGORY_LABELS,
|
||||
ROLE_CATEGORY_ORDER,
|
||||
resolveAgent,
|
||||
PIPELINE_TEMPLATES,
|
||||
} from './features/company';
|
||||
import { AIService } from './core/services';
|
||||
|
||||
@@ -102,6 +103,21 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
*/
|
||||
private _companyAbort?: AbortController;
|
||||
|
||||
/**
|
||||
* Open approval gates. The dispatcher emits `phase: 'awaiting-approval'`
|
||||
* for stages with `requiresApproval`, and waits on a Promise this map
|
||||
* stores. The webview surfaces 승인 / 수정요청 / 중단 buttons; clicks
|
||||
* route through `chatHandlers.respondCompanyApproval` which calls
|
||||
* `resolveApprovalGate(stageId, decision)` here.
|
||||
*
|
||||
* Keyed by stageId — only one approval may be pending per stage at a
|
||||
* time (sequential dispatch), but multiple stages across the same turn
|
||||
* each get their own entry as they hit their gate. On turn abort we
|
||||
* resolve all outstanding entries with `{ kind: 'abort' }` so the
|
||||
* dispatcher unblocks cleanly.
|
||||
*/
|
||||
private _pendingApprovals = new Map<string, (d: import('./features/company/dispatcher').ApprovalDecision) => void>();
|
||||
|
||||
/** Active file-system watcher for the project-architecture auto-update. Disposed on switch/detach. */
|
||||
private _archWatcher?: vscode.FileSystemWatcher;
|
||||
/** Debounce timer for the architecture watcher. */
|
||||
@@ -1475,6 +1491,25 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
abortCompanyTurn(): boolean {
|
||||
if (!this._companyAbort) return false;
|
||||
this._companyAbort.abort();
|
||||
// 승인 게이트 대기 중인 모든 stage를 'abort'로 해제. 안 하면 dispatcher가
|
||||
// 영원히 await 상태로 남아 turn이 절대 종료 안 됨.
|
||||
for (const resolve of this._pendingApprovals.values()) {
|
||||
try { resolve({ kind: 'abort' }); } catch { /* noop */ }
|
||||
}
|
||||
this._pendingApprovals.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by chatHandlers when the user clicks an approval card button.
|
||||
* Resolves the dispatcher's awaitApproval promise for `stageId`. Idempotent
|
||||
* — extra clicks after the first one are silently dropped.
|
||||
*/
|
||||
resolveApprovalGate(stageId: string, decision: import('./features/company/dispatcher').ApprovalDecision): boolean {
|
||||
const resolve = this._pendingApprovals.get(stageId);
|
||||
if (!resolve) return false;
|
||||
this._pendingApprovals.delete(stageId);
|
||||
try { resolve(decision); } catch { /* noop */ }
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1495,6 +1530,16 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
for (const [cat, defs] of Object.entries(byCategory)) {
|
||||
slimByCategory[cat] = defs.map((d) => ({ id: d.id, name: d.name, emoji: d.emoji }));
|
||||
}
|
||||
// 템플릿 카탈로그 — 가벼운 메타데이터만 (stages는 stamp 시점에 한 번
|
||||
// 더 요청). UI는 dropdown 옵션 텍스트만 필요.
|
||||
const templates = PIPELINE_TEMPLATES.map((t) => ({
|
||||
templateId: t.templateId,
|
||||
name: t.name,
|
||||
description: t.description,
|
||||
stageCount: t.stages.length,
|
||||
suggestedPipelineId: t.suggestedPipelineId,
|
||||
suggestedPipelineName: t.suggestedPipelineName,
|
||||
}));
|
||||
this._view.webview.postMessage({
|
||||
type: 'companyPipelines',
|
||||
value: {
|
||||
@@ -1503,6 +1548,7 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
roleCategoryLabels: ROLE_CATEGORY_LABELS,
|
||||
roleCategoryOrder: ROLE_CATEGORY_ORDER,
|
||||
activeAgentsByCategory: slimByCategory,
|
||||
templates,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1630,6 +1676,17 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
executeActionTags: (text) => this._agent.executeActionTagsOnText(text),
|
||||
signal: abort.signal,
|
||||
onEvent: emit,
|
||||
// 승인 게이트 bridge — dispatcher가 호출하면 Promise를 만들어
|
||||
// resolver를 _pendingApprovals에 보관 후 await. 사용자가 카드 버튼을
|
||||
// 누르면 chatHandlers가 resolveApprovalGate(stageId, decision)을 호출
|
||||
// 하고 그 resolve가 이 await을 풀어준다.
|
||||
awaitApproval: ({ stageId }) => new Promise((resolve) => {
|
||||
if (abort.signal.aborted) {
|
||||
resolve({ kind: 'abort' });
|
||||
return;
|
||||
}
|
||||
this._pendingApprovals.set(stageId, resolve);
|
||||
}),
|
||||
});
|
||||
} catch (e: any) {
|
||||
logError('company.runTurn: unexpected failure.', { error: e?.message ?? String(e) });
|
||||
|
||||
Reference in New Issue
Block a user