Bump version to 2.35.0: Knowledge Resilience & Standardization Milestone.
This commit is contained in:
+761
-4
@@ -15,6 +15,7 @@ import {
|
||||
import { getConfig } from './config';
|
||||
import { AgentExecutor, ChatMessage } from './agent';
|
||||
import { BridgeInterface } from './bridge';
|
||||
import { buildProjectChronicleGuardContext, ProjectChronicleManager, ProjectProfile } from './features/projectChronicle';
|
||||
|
||||
interface LastVisibleChatSnapshot {
|
||||
history: ChatMessage[];
|
||||
@@ -42,10 +43,13 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
private static readonly lastVisibleChatStateKey = 'g1nation.lastVisibleChat';
|
||||
private static readonly blankChatStateKey = 'g1nation.blankChatActive';
|
||||
private static readonly lastAgentStateKey = 'g1nation.lastAgentPath';
|
||||
private static readonly chronicleProjectsStateKey = 'g1nation.chronicleProjects';
|
||||
private static readonly activeChronicleProjectStateKey = 'g1nation.activeChronicleProjectId';
|
||||
private _view?: vscode.WebviewView;
|
||||
public brainEnabled = true;
|
||||
private _currentSessionBrainId: string | null = null;
|
||||
private _currentNegativePrompt: string = '';
|
||||
private readonly _chronicle = new ProjectChronicleManager();
|
||||
|
||||
constructor(
|
||||
private readonly _extensionUri: vscode.Uri,
|
||||
@@ -89,6 +93,7 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
await this._sendSessionList();
|
||||
await this._sendModels();
|
||||
await this._sendConfig();
|
||||
await this._sendChronicleProjects();
|
||||
await this._restoreActiveSessionIntoView();
|
||||
break;
|
||||
case 'toggleMultiAgent':
|
||||
@@ -103,6 +108,45 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
case 'getAgents':
|
||||
await this._sendAgentsList();
|
||||
break;
|
||||
case 'getChronicleProjects':
|
||||
await this._sendChronicleProjects();
|
||||
break;
|
||||
case 'createChronicleProject':
|
||||
await this._createChronicleProject();
|
||||
break;
|
||||
case 'setChronicleProject':
|
||||
await this._setActiveChronicleProject(data.id);
|
||||
break;
|
||||
case 'openChronicleFolder':
|
||||
await this._openChronicleFolder();
|
||||
break;
|
||||
case 'getChronicleRecords':
|
||||
await this._sendChronicleRecords();
|
||||
break;
|
||||
case 'openChronicleRecord':
|
||||
await this._openChronicleRecord(data.path);
|
||||
break;
|
||||
case 'writeChroniclePlanning':
|
||||
await this._writeChroniclePlanningFromCurrentChat();
|
||||
break;
|
||||
case 'writeChronicleDiscussion':
|
||||
await this._writeChronicleDiscussionFromCurrentChat();
|
||||
break;
|
||||
case 'writeChronicleDecision':
|
||||
await this._writeChronicleDecisionFromInput();
|
||||
break;
|
||||
case 'writeChronicleDevelopment':
|
||||
await this._writeChronicleDevelopmentFromCurrentChat();
|
||||
break;
|
||||
case 'writeChronicleBug':
|
||||
await this._writeChronicleBugFromInput();
|
||||
break;
|
||||
case 'writeChronicleRetrospective':
|
||||
await this._writeChronicleRetrospectiveFromInput();
|
||||
break;
|
||||
case 'writeChronicleRecord':
|
||||
await this._writeChronicleRecord(data.recordType);
|
||||
break;
|
||||
case 'createAgent':
|
||||
await this._createAgent();
|
||||
break;
|
||||
@@ -899,6 +943,568 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
});
|
||||
}
|
||||
|
||||
private _getChronicleProjects(): ProjectProfile[] {
|
||||
const raw = this._context.globalState.get<ProjectProfile[]>(SidebarChatProvider.chronicleProjectsStateKey, []) || [];
|
||||
const valid = raw.filter((profile: ProjectProfile) =>
|
||||
profile
|
||||
&& typeof profile.projectId === 'string'
|
||||
&& typeof profile.projectName === 'string'
|
||||
&& typeof profile.recordRoot === 'string'
|
||||
);
|
||||
|
||||
if (valid.length > 0) return valid;
|
||||
|
||||
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
|
||||
if (!workspaceRoot) return [];
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const projectName = path.basename(workspaceRoot) || 'Current Project';
|
||||
return [{
|
||||
projectId: this._slugify(projectName),
|
||||
projectName,
|
||||
projectRoot: workspaceRoot,
|
||||
recordRoot: path.join(workspaceRoot, 'docs', 'records', projectName),
|
||||
description: 'Auto-detected current workspace project.',
|
||||
corePurpose: 'Capture project planning, decisions, development notes, bugs, and retrospectives as Markdown.',
|
||||
targetUsers: ['Project developer'],
|
||||
avoidDirections: ['Do not tightly couple records to chat execution internals.'],
|
||||
detailLevel: 'standard',
|
||||
createdAt: now,
|
||||
updatedAt: now
|
||||
}];
|
||||
}
|
||||
|
||||
private async _putChronicleProjects(projects: ProjectProfile[]) {
|
||||
await this._context.globalState.update(SidebarChatProvider.chronicleProjectsStateKey, projects);
|
||||
}
|
||||
|
||||
private _getActiveChronicleProject(): ProjectProfile | null {
|
||||
const projects = this._getChronicleProjects();
|
||||
if (projects.length === 0) return null;
|
||||
const activeId = this._context.globalState.get<string>(SidebarChatProvider.activeChronicleProjectStateKey, '');
|
||||
return projects.find(project => project.projectId === activeId) || projects[0];
|
||||
}
|
||||
|
||||
private async _sendChronicleProjects() {
|
||||
if (!this._view) return;
|
||||
const projects = this._getChronicleProjects();
|
||||
const active = this._getActiveChronicleProject();
|
||||
this._view.webview.postMessage({
|
||||
type: 'chronicleProjects',
|
||||
value: {
|
||||
activeProjectId: active?.projectId || '',
|
||||
projects: projects.map(project => ({
|
||||
id: project.projectId,
|
||||
name: project.projectName,
|
||||
root: project.projectRoot || '',
|
||||
recordRoot: project.recordRoot,
|
||||
description: project.description || ''
|
||||
}))
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async _createChronicleProject() {
|
||||
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || '';
|
||||
const defaultName = workspaceRoot ? path.basename(workspaceRoot) : 'New Project';
|
||||
|
||||
const projectName = await vscode.window.showInputBox({
|
||||
prompt: 'Project name for Chronicle records',
|
||||
value: defaultName,
|
||||
validateInput: (value) => value.trim() ? null : 'Project name is required.'
|
||||
});
|
||||
if (!projectName) return;
|
||||
|
||||
const description = await vscode.window.showInputBox({
|
||||
prompt: 'One-line project description',
|
||||
value: 'Project planning, decisions, development logs, and bug records.'
|
||||
});
|
||||
if (description === undefined) return;
|
||||
|
||||
const projectRoot = await vscode.window.showInputBox({
|
||||
prompt: 'Project root path',
|
||||
value: workspaceRoot,
|
||||
validateInput: (value) => value.trim() ? null : 'Project root is required.'
|
||||
});
|
||||
if (!projectRoot) return;
|
||||
|
||||
const defaultRecordRoot = path.join(projectRoot.trim(), 'docs', 'records', projectName.trim());
|
||||
const recordRoot = await vscode.window.showInputBox({
|
||||
prompt: 'Markdown record folder path',
|
||||
value: defaultRecordRoot,
|
||||
validateInput: (value) => value.trim() ? null : 'Record folder path is required.'
|
||||
});
|
||||
if (!recordRoot) return;
|
||||
|
||||
const corePurpose = await vscode.window.showInputBox({
|
||||
prompt: 'Core project purpose or guardrail',
|
||||
value: 'Keep project knowledge traceable through Markdown records.'
|
||||
});
|
||||
if (corePurpose === undefined) return;
|
||||
|
||||
const detailChoice = await vscode.window.showQuickPick([
|
||||
{ label: 'standard', description: 'Request, question intent, decisions, planning, development, and bugs' },
|
||||
{ label: 'simple', description: 'Request summary, decisions, and implementation result' },
|
||||
{ label: 'detailed', description: 'Standard records plus alternatives, lessons, and retrospectives' }
|
||||
], {
|
||||
placeHolder: 'Chronicle record detail level'
|
||||
});
|
||||
if (!detailChoice) return;
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const projects = this._getChronicleProjects();
|
||||
const idBase = this._slugify(projectName.trim());
|
||||
let projectId = idBase;
|
||||
let suffix = 2;
|
||||
while (projects.some(project => project.projectId === projectId)) {
|
||||
projectId = `${idBase}-${suffix++}`;
|
||||
}
|
||||
|
||||
const profile: ProjectProfile = {
|
||||
projectId,
|
||||
projectName: projectName.trim(),
|
||||
projectRoot: projectRoot.trim(),
|
||||
recordRoot: recordRoot.trim(),
|
||||
description: description.trim(),
|
||||
corePurpose: corePurpose.trim(),
|
||||
targetUsers: ['Project developer'],
|
||||
avoidDirections: ['Do not mix records across projects.', 'Do not tightly couple records to core agent execution.'],
|
||||
detailLevel: detailChoice.label as ProjectProfile['detailLevel'],
|
||||
createdAt: now,
|
||||
updatedAt: now
|
||||
};
|
||||
|
||||
this._chronicle.ensureProject(profile);
|
||||
const nextProjects = [...projects.filter(project => project.projectId !== profile.projectId), profile];
|
||||
await this._putChronicleProjects(nextProjects);
|
||||
await this._context.globalState.update(SidebarChatProvider.activeChronicleProjectStateKey, profile.projectId);
|
||||
await this._sendChronicleProjects();
|
||||
this.injectSystemMessage(`**[Designer Project Created]** ${profile.projectName}\n\`${profile.recordRoot}\``);
|
||||
}
|
||||
|
||||
private async _setActiveChronicleProject(projectId: string) {
|
||||
if (!projectId || projectId === 'new') {
|
||||
await this._createChronicleProject();
|
||||
return;
|
||||
}
|
||||
|
||||
const target = this._getChronicleProjects().find(project => project.projectId === projectId);
|
||||
if (!target) return;
|
||||
await this._context.globalState.update(SidebarChatProvider.activeChronicleProjectStateKey, target.projectId);
|
||||
await this._sendChronicleProjects();
|
||||
await this._sendChronicleRecords();
|
||||
this.injectSystemMessage(`**[Designer Project Selected]** ${target.projectName}\n\`${target.recordRoot}\``);
|
||||
}
|
||||
|
||||
private async _openChronicleFolder() {
|
||||
const profile = this._getActiveChronicleProject();
|
||||
if (!profile) {
|
||||
vscode.window.showWarningMessage('No Chronicle project is selected.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this._chronicle.ensureProject(profile);
|
||||
await vscode.commands.executeCommand('revealFileInOS', vscode.Uri.file(profile.recordRoot));
|
||||
} catch (err: any) {
|
||||
vscode.window.showErrorMessage(`Failed to open Chronicle folder: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async _sendChronicleRecords() {
|
||||
if (!this._view) return;
|
||||
const profile = this._getActiveChronicleProject();
|
||||
if (!profile) {
|
||||
this._view.webview.postMessage({ type: 'chronicleRecords', value: [] });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const records = this._chronicle.listRecords(profile).map(record => ({
|
||||
section: record.section,
|
||||
fileName: record.fileName,
|
||||
path: record.filePath,
|
||||
relativePath: record.relativePath,
|
||||
updatedAt: record.updatedAt
|
||||
}));
|
||||
this._view.webview.postMessage({ type: 'chronicleRecords', value: records });
|
||||
} catch (err: any) {
|
||||
vscode.window.showErrorMessage(`Failed to list Chronicle records: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async _openChronicleRecord(recordPath: string) {
|
||||
const profile = this._getActiveChronicleProject();
|
||||
if (!profile || !recordPath) {
|
||||
vscode.window.showWarningMessage('Select a Chronicle record first.');
|
||||
return;
|
||||
}
|
||||
|
||||
const root = path.resolve(profile.recordRoot);
|
||||
const target = path.resolve(recordPath);
|
||||
if (!target.startsWith(`${root}${path.sep}`) || path.extname(target) !== '.md') {
|
||||
vscode.window.showErrorMessage('Selected Chronicle record path is not valid.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fs.existsSync(target)) {
|
||||
vscode.window.showErrorMessage('Selected Chronicle record no longer exists.');
|
||||
await this._sendChronicleRecords();
|
||||
return;
|
||||
}
|
||||
|
||||
const doc = await vscode.workspace.openTextDocument(target);
|
||||
await vscode.window.showTextDocument(doc);
|
||||
}
|
||||
|
||||
private async _writeChroniclePlanningFromCurrentChat() {
|
||||
const profile = this._getActiveChronicleProject();
|
||||
if (!profile) {
|
||||
vscode.window.showWarningMessage('Select or create a Designer project first.');
|
||||
return;
|
||||
}
|
||||
|
||||
const history = this._agent.getHistory();
|
||||
const latestUser = [...history].reverse().find(message => message.role === 'user')?.content || '';
|
||||
const latestAssistant = [...history].reverse().find(message => message.role === 'assistant')?.content || '';
|
||||
const featureName = await vscode.window.showInputBox({
|
||||
prompt: 'Feature name for the planning document',
|
||||
value: this._summarizeForTitle(latestUser || 'Project Chronicle Feature')
|
||||
});
|
||||
if (!featureName) return;
|
||||
|
||||
try {
|
||||
const createdAt = new Date().toISOString();
|
||||
const result = this._chronicle.writePlanning(profile, {
|
||||
featureName: featureName.trim(),
|
||||
purpose: 'Record the reason, scope, direction, and success criteria before implementation.',
|
||||
background: this._summarizeTextForWiki(latestAssistant || latestUser),
|
||||
userIntent: this._summarizeTextForWiki(latestUser),
|
||||
sourceRequest: latestUser || 'No user request captured in the current chat.',
|
||||
scope: [
|
||||
'Create a project-specific planning record.',
|
||||
'Capture user intent and implementation direction.',
|
||||
'Keep the record independent from chat execution internals.'
|
||||
],
|
||||
outOfScope: [
|
||||
'Full automatic transcript capture.',
|
||||
'External database integration.',
|
||||
'Git automation.'
|
||||
],
|
||||
developmentDirection: 'Use Project Chronicle as a low-dependency Markdown record layer.',
|
||||
dependencyStrategy: 'Use local filesystem writes through the independent projectChronicle module.',
|
||||
expectedValue: 'Future work can understand why this feature exists and what decisions shaped it.',
|
||||
successCriteria: [
|
||||
'The planning document is created under the selected project record folder.',
|
||||
'The document includes user intent, scope, out-of-scope items, and success criteria.'
|
||||
],
|
||||
developerInstruction: 'Use this document as the implementation guardrail for the next development step.',
|
||||
createdAt
|
||||
});
|
||||
this._chronicle.appendTimeline(profile, [`Planning record created: ${result.relativePath}`], createdAt);
|
||||
vscode.window.showInformationMessage(`Chronicle planning saved: ${result.relativePath}`);
|
||||
await this._sendChronicleRecords();
|
||||
this.injectSystemMessage(`**[Chronicle Planning Saved]** \`${result.filePath}\``);
|
||||
} catch (err: any) {
|
||||
vscode.window.showErrorMessage(`Failed to write planning record: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async _writeChronicleDiscussionFromCurrentChat() {
|
||||
const profile = this._getActiveChronicleProject();
|
||||
if (!profile) {
|
||||
vscode.window.showWarningMessage('Select or create a Designer project first.');
|
||||
return;
|
||||
}
|
||||
|
||||
const history = this._agent.getHistory();
|
||||
const latestUser = [...history].reverse().find(message => message.role === 'user')?.content || '';
|
||||
const latestAssistant = [...history].reverse().find(message => message.role === 'assistant')?.content || '';
|
||||
const title = await vscode.window.showInputBox({
|
||||
prompt: 'Discussion title',
|
||||
value: this._summarizeForTitle(latestUser || 'Project Discussion')
|
||||
});
|
||||
if (!title) return;
|
||||
|
||||
const question = await vscode.window.showInputBox({
|
||||
prompt: 'AI question to record (optional)',
|
||||
value: ''
|
||||
});
|
||||
if (question === undefined) return;
|
||||
|
||||
let questions: any[] = [];
|
||||
if (question.trim()) {
|
||||
const reason = await vscode.window.showInputBox({
|
||||
prompt: 'Why was this question asked?',
|
||||
value: 'To avoid writing records to the wrong project or making an unclear design decision.'
|
||||
});
|
||||
if (reason === undefined) return;
|
||||
|
||||
const impact = await vscode.window.showInputBox({
|
||||
prompt: 'How does this question affect the decision?',
|
||||
value: 'It determines the correct project context, scope, or implementation path.'
|
||||
});
|
||||
if (impact === undefined) return;
|
||||
|
||||
questions = [{
|
||||
question: question.trim(),
|
||||
reason: reason.trim(),
|
||||
expectedInformation: 'Information needed to clarify project context, scope, or decision direction.',
|
||||
impactOnDecision: impact.trim()
|
||||
}];
|
||||
}
|
||||
|
||||
try {
|
||||
const createdAt = new Date().toISOString();
|
||||
const result = this._chronicle.writeDiscussion(profile, {
|
||||
title: title.trim(),
|
||||
userRequest: this._summarizeTextForWiki(latestUser || 'No user request captured in the current chat.'),
|
||||
interpretedIntent: 'Capture the discussion as a reusable project knowledge record instead of leaving it only in chat history.',
|
||||
questions,
|
||||
discussions: [
|
||||
this._summarizeTextForWiki(latestAssistant || latestUser || 'No discussion detail captured yet.')
|
||||
],
|
||||
decisions: [],
|
||||
createdAt
|
||||
});
|
||||
this._chronicle.appendTimeline(profile, [`Discussion record created: ${result.relativePath}`], createdAt);
|
||||
vscode.window.showInformationMessage(`Chronicle discussion saved: ${result.relativePath}`);
|
||||
await this._sendChronicleRecords();
|
||||
this.injectSystemMessage(`**[Chronicle Discussion Saved]** \`${result.filePath}\``);
|
||||
} catch (err: any) {
|
||||
vscode.window.showErrorMessage(`Failed to write discussion record: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async _writeChronicleDecisionFromInput() {
|
||||
const profile = this._getActiveChronicleProject();
|
||||
if (!profile) {
|
||||
vscode.window.showWarningMessage('Select or create a Designer project first.');
|
||||
return;
|
||||
}
|
||||
|
||||
const title = await vscode.window.showInputBox({
|
||||
prompt: 'Decision title',
|
||||
value: 'Use independent Markdown record module'
|
||||
});
|
||||
if (!title) return;
|
||||
|
||||
const decision = await vscode.window.showInputBox({
|
||||
prompt: 'Decision',
|
||||
value: 'Implement this behavior as an independent Project Chronicle module.'
|
||||
});
|
||||
if (decision === undefined) return;
|
||||
|
||||
const reason = await vscode.window.showInputBox({
|
||||
prompt: 'Decision reason',
|
||||
value: 'To reduce coupling and keep project records portable.'
|
||||
});
|
||||
if (reason === undefined) return;
|
||||
|
||||
const alternatives = await vscode.window.showInputBox({
|
||||
prompt: 'Rejected alternatives (comma-separated)',
|
||||
value: 'Integrate with Second Brain, integrate directly into Agent execution'
|
||||
});
|
||||
if (alternatives === undefined) return;
|
||||
|
||||
try {
|
||||
const createdAt = new Date().toISOString();
|
||||
const adrNumber = this._chronicle.nextAdrNumber(profile);
|
||||
const result = this._chronicle.writeDecision(profile, {
|
||||
title: title.trim(),
|
||||
status: 'accepted',
|
||||
context: 'A project record needs to capture not only what changed, but why the direction was chosen.',
|
||||
decision: decision.trim(),
|
||||
reason: reason.trim(),
|
||||
alternatives: alternatives.split(',').map(item => item.trim()).filter(Boolean),
|
||||
consequences: [
|
||||
'Records can evolve independently from chat and agent internals.',
|
||||
'Future automation can emit chronicle events without owning the core execution path.'
|
||||
],
|
||||
createdAt
|
||||
}, adrNumber);
|
||||
this._chronicle.appendTimeline(profile, [`Decision record created: ${result.relativePath}`], createdAt);
|
||||
vscode.window.showInformationMessage(`Chronicle decision saved: ${result.relativePath}`);
|
||||
await this._sendChronicleRecords();
|
||||
this.injectSystemMessage(`**[Chronicle Decision Saved]** \`${result.filePath}\``);
|
||||
} catch (err: any) {
|
||||
vscode.window.showErrorMessage(`Failed to write decision record: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async _writeChronicleDevelopmentFromCurrentChat() {
|
||||
const profile = this._getActiveChronicleProject();
|
||||
if (!profile) {
|
||||
vscode.window.showWarningMessage('Select or create a Designer project first.');
|
||||
return;
|
||||
}
|
||||
|
||||
const history = this._agent.getHistory();
|
||||
const latestUser = [...history].reverse().find(message => message.role === 'user')?.content || '';
|
||||
const latestAssistant = [...history].reverse().find(message => message.role === 'assistant')?.content || '';
|
||||
const featureName = await vscode.window.showInputBox({
|
||||
prompt: 'Feature name for the development log',
|
||||
value: this._summarizeForTitle(latestUser || 'Implementation Log')
|
||||
});
|
||||
if (!featureName) return;
|
||||
|
||||
try {
|
||||
const createdAt = new Date().toISOString();
|
||||
const result = this._chronicle.writeDevelopmentLog(profile, {
|
||||
featureName: featureName.trim(),
|
||||
purpose: 'Record the actual implementation outcome for later maintenance.',
|
||||
implementationSummary: this._summarizeTextForWiki(latestAssistant || 'Implementation summary was not captured from chat yet.'),
|
||||
architecture: 'Project Chronicle records are written through an independent Markdown module.',
|
||||
changedFiles: ['Capture exact changed files after verification.'],
|
||||
dependencyNotes: 'Keep dependencies limited to TypeScript, Node fs/path, and VS Code extension APIs.',
|
||||
bugs: [],
|
||||
lessons: [
|
||||
'Write implementation notes as soon as a stable development step finishes.',
|
||||
'Keep generated records project-specific.'
|
||||
],
|
||||
createdAt
|
||||
});
|
||||
this._chronicle.appendTimeline(profile, [`Development log created: ${result.relativePath}`], createdAt);
|
||||
vscode.window.showInformationMessage(`Chronicle development log saved: ${result.relativePath}`);
|
||||
await this._sendChronicleRecords();
|
||||
this.injectSystemMessage(`**[Chronicle Development Saved]** \`${result.filePath}\``);
|
||||
} catch (err: any) {
|
||||
vscode.window.showErrorMessage(`Failed to write development record: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async _writeChronicleBugFromInput() {
|
||||
const profile = this._getActiveChronicleProject();
|
||||
if (!profile) {
|
||||
vscode.window.showWarningMessage('Select or create a Designer project first.');
|
||||
return;
|
||||
}
|
||||
|
||||
const title = await vscode.window.showInputBox({
|
||||
prompt: 'Bug title',
|
||||
value: 'record-generation-issue'
|
||||
});
|
||||
if (!title) return;
|
||||
|
||||
const symptom = await vscode.window.showInputBox({
|
||||
prompt: 'Bug symptom',
|
||||
value: 'Describe what failed or looked wrong.'
|
||||
});
|
||||
if (symptom === undefined) return;
|
||||
|
||||
const cause = await vscode.window.showInputBox({
|
||||
prompt: 'Bug cause',
|
||||
value: 'Cause is not confirmed yet.'
|
||||
});
|
||||
if (cause === undefined) return;
|
||||
|
||||
const fix = await vscode.window.showInputBox({
|
||||
prompt: 'Fix',
|
||||
value: 'Describe the fix or mitigation.'
|
||||
});
|
||||
if (fix === undefined) return;
|
||||
|
||||
try {
|
||||
const createdAt = new Date().toISOString();
|
||||
const bugNumber = this._chronicle.nextBugNumber(profile);
|
||||
const result = this._chronicle.writeBug(profile, {
|
||||
title: title.trim(),
|
||||
symptom: symptom.trim(),
|
||||
cause: cause.trim(),
|
||||
fix: fix.trim(),
|
||||
prevention: 'Validate project selection, record path, and write permissions before generating files.',
|
||||
createdAt
|
||||
}, bugNumber);
|
||||
this._chronicle.appendTimeline(profile, [`Bug record created: ${result.relativePath}`], createdAt);
|
||||
vscode.window.showInformationMessage(`Chronicle bug record saved: ${result.relativePath}`);
|
||||
await this._sendChronicleRecords();
|
||||
this.injectSystemMessage(`**[Chronicle Bug Saved]** \`${result.filePath}\``);
|
||||
} catch (err: any) {
|
||||
vscode.window.showErrorMessage(`Failed to write bug record: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async _writeChronicleRetrospectiveFromInput() {
|
||||
const profile = this._getActiveChronicleProject();
|
||||
if (!profile) {
|
||||
vscode.window.showWarningMessage('Select or create a Designer project first.');
|
||||
return;
|
||||
}
|
||||
|
||||
const title = await vscode.window.showInputBox({
|
||||
prompt: 'Retrospective title',
|
||||
value: 'Project Chronicle Guard iteration'
|
||||
});
|
||||
if (!title) return;
|
||||
|
||||
const summary = await vscode.window.showInputBox({
|
||||
prompt: 'Work summary',
|
||||
value: 'Completed an incremental development step and recorded the outcome.'
|
||||
});
|
||||
if (summary === undefined) return;
|
||||
|
||||
const wentWell = await vscode.window.showInputBox({
|
||||
prompt: 'What went well? (comma-separated)',
|
||||
value: 'Kept the feature independent, Generated Markdown records, Preserved project context'
|
||||
});
|
||||
if (wentWell === undefined) return;
|
||||
|
||||
const toImprove = await vscode.window.showInputBox({
|
||||
prompt: 'What should improve? (comma-separated)',
|
||||
value: 'More automatic question intent capture, Richer record editing UI'
|
||||
});
|
||||
if (toImprove === undefined) return;
|
||||
|
||||
const nextActions = await vscode.window.showInputBox({
|
||||
prompt: 'Next actions (comma-separated)',
|
||||
value: 'Add tests, Improve Designer UI, Add event-based record capture'
|
||||
});
|
||||
if (nextActions === undefined) return;
|
||||
|
||||
try {
|
||||
const createdAt = new Date().toISOString();
|
||||
const result = this._chronicle.writeRetrospective(profile, {
|
||||
title: title.trim(),
|
||||
summary: summary.trim(),
|
||||
wentWell: wentWell.split(',').map(item => item.trim()).filter(Boolean),
|
||||
toImprove: toImprove.split(',').map(item => item.trim()).filter(Boolean),
|
||||
nextActions: nextActions.split(',').map(item => item.trim()).filter(Boolean),
|
||||
createdAt
|
||||
});
|
||||
this._chronicle.appendTimeline(profile, [`Retrospective created: ${result.relativePath}`], createdAt);
|
||||
vscode.window.showInformationMessage(`Chronicle retrospective saved: ${result.relativePath}`);
|
||||
await this._sendChronicleRecords();
|
||||
this.injectSystemMessage(`**[Chronicle Retrospective Saved]** \`${result.filePath}\``);
|
||||
} catch (err: any) {
|
||||
vscode.window.showErrorMessage(`Failed to write retrospective record: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async _writeChronicleRecord(recordType: string) {
|
||||
switch (recordType) {
|
||||
case 'planning':
|
||||
await this._writeChroniclePlanningFromCurrentChat();
|
||||
break;
|
||||
case 'discussion':
|
||||
await this._writeChronicleDiscussionFromCurrentChat();
|
||||
break;
|
||||
case 'decision':
|
||||
await this._writeChronicleDecisionFromInput();
|
||||
break;
|
||||
case 'development':
|
||||
await this._writeChronicleDevelopmentFromCurrentChat();
|
||||
break;
|
||||
case 'bug':
|
||||
await this._writeChronicleBugFromInput();
|
||||
break;
|
||||
case 'retrospective':
|
||||
await this._writeChronicleRetrospectiveFromInput();
|
||||
break;
|
||||
default:
|
||||
vscode.window.showWarningMessage('Select a Chronicle record type first.');
|
||||
}
|
||||
}
|
||||
|
||||
private _getAgentsDir(): string {
|
||||
const defaultPath = 'E:\\Wiki\\Agent\\.agent\\skills';
|
||||
if (fs.existsSync(defaultPath)) return defaultPath;
|
||||
@@ -1043,7 +1649,7 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
private async _handlePrompt(data: any) {
|
||||
if (!this._view) return;
|
||||
|
||||
const { value, model, internet, files, agentFile, negativePrompt } = data;
|
||||
const { value, model, internet, files, agentFile, negativePrompt, designerGuard, secondBrainTrace, secondBrainTraceDebug } = data;
|
||||
this._currentNegativePrompt = negativePrompt || '';
|
||||
this._currentSessionBrainId = getActiveBrainProfile().id;
|
||||
|
||||
@@ -1052,12 +1658,17 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
agentSkillContext = fs.readFileSync(agentFile, 'utf8');
|
||||
}
|
||||
|
||||
const designerContext = designerGuard !== false ? this._buildDesignerGuardContext() : undefined;
|
||||
|
||||
try {
|
||||
await this._agent.handlePrompt(value, model, {
|
||||
internetEnabled: internet,
|
||||
visionContent: files,
|
||||
agentSkillContext,
|
||||
negativePrompt
|
||||
negativePrompt,
|
||||
designerContext,
|
||||
secondBrainTraceEnabled: secondBrainTrace !== false,
|
||||
secondBrainTraceDebug: !!secondBrainTraceDebug
|
||||
});
|
||||
} catch (error: any) {
|
||||
logError('Prompt handling failed in sidebar provider.', { error: error?.message || String(error), promptPreview: summarizeText(value || '', 200) });
|
||||
@@ -1065,6 +1676,10 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
}
|
||||
}
|
||||
|
||||
private _buildDesignerGuardContext(): string {
|
||||
return buildProjectChronicleGuardContext(this._getActiveChronicleProject());
|
||||
}
|
||||
|
||||
private async _sendModels() {
|
||||
if (!this._view) return;
|
||||
try {
|
||||
@@ -1755,6 +2370,9 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
<div class="header-actions">
|
||||
<button class="icon-btn" id="newChatBtn" data-tooltip="New Chat">New</button>
|
||||
<button class="icon-btn" id="saveWikiRawBtn" data-tooltip="Save Wiki Raw">Wiki</button>
|
||||
<button class="icon-btn active" id="designerGuardBtn" data-tooltip="Chronicle Guard Mode: Auto">Guard</button>
|
||||
<button class="icon-btn active" id="brainTraceBtn" data-tooltip="Second Brain Trace Mode">Trace</button>
|
||||
<button class="icon-btn" id="brainTraceDebugBtn" data-tooltip="Second Brain Debug JSON">Dbg</button>
|
||||
<button class="icon-btn" id="multiAgentBtn" data-tooltip="Multi-Agent Mode">MA</button>
|
||||
<button class="icon-btn" id="internetBtn" data-tooltip="Internet Access">Web</button>
|
||||
<button class="icon-btn" id="historyBtn" data-tooltip="View History">Log</button>
|
||||
@@ -1786,6 +2404,35 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-row">
|
||||
<div class="select-wrap"><select id="designerSel" title="Select Designer Project"></select></div>
|
||||
<div class="tool-group" aria-label="Designer actions">
|
||||
<button class="icon-btn" id="addDesignerBtn" data-tooltip="Create Designer Project">Add</button>
|
||||
<button class="icon-btn" id="openDesignerBtn" data-tooltip="Open Record Folder">Open</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-row">
|
||||
<div class="select-wrap">
|
||||
<select id="chronicleRecordTypeSel" title="Select Chronicle Record Type">
|
||||
<option value="planning">Planning</option>
|
||||
<option value="discussion">Discussion</option>
|
||||
<option value="decision">Decision</option>
|
||||
<option value="development">Development</option>
|
||||
<option value="bug">Bug</option>
|
||||
<option value="retrospective">Retrospective</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="tool-group" aria-label="Chronicle write actions">
|
||||
<button class="icon-btn" id="writeChronicleBtn" data-tooltip="Write Selected Record">Write</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-row">
|
||||
<div class="select-wrap"><select id="chronicleRecordSel" title="Select Chronicle Record"></select></div>
|
||||
<div class="tool-group" aria-label="Chronicle record actions">
|
||||
<button class="icon-btn" id="refreshChronicleRecordsBtn" data-tooltip="Refresh Records">Ref</button>
|
||||
<button class="icon-btn" id="openChronicleRecordBtn" data-tooltip="Open Selected Record">Open</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1864,7 +2511,13 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
}
|
||||
|
||||
function saveWebviewState(history) {
|
||||
vscode.setState({ history });
|
||||
const current = vscode.getState() || {};
|
||||
vscode.setState({ ...current, history });
|
||||
}
|
||||
|
||||
function saveUiState() {
|
||||
const current = vscode.getState() || {};
|
||||
vscode.setState({ ...current, designerGuardEnabled, secondBrainTraceEnabled, secondBrainTraceDebug });
|
||||
}
|
||||
|
||||
function renderHistory(history) {
|
||||
@@ -1989,6 +2642,9 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
const fileInput = document.getElementById('fileInput');
|
||||
const attachPreview = document.getElementById('attachPreview');
|
||||
const agentSel = document.getElementById('agentSel');
|
||||
const designerSel = document.getElementById('designerSel');
|
||||
const chronicleRecordTypeSel = document.getElementById('chronicleRecordTypeSel');
|
||||
const chronicleRecordSel = document.getElementById('chronicleRecordSel');
|
||||
const editAgentBtn = document.getElementById('editAgentBtn');
|
||||
const addAgentBtn = document.getElementById('addAgentBtn');
|
||||
const deleteAgentBtn = document.getElementById('deleteAgentBtn');
|
||||
@@ -2003,8 +2659,29 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
|
||||
let streamBody = null;
|
||||
let internetEnabled = false;
|
||||
let designerGuardEnabled = true;
|
||||
let secondBrainTraceEnabled = true;
|
||||
let secondBrainTraceDebug = false;
|
||||
let pendingFiles = [];
|
||||
let editMode = false;
|
||||
if (previousState && typeof previousState.designerGuardEnabled === 'boolean') {
|
||||
designerGuardEnabled = previousState.designerGuardEnabled;
|
||||
}
|
||||
if (previousState && typeof previousState.secondBrainTraceEnabled === 'boolean') {
|
||||
secondBrainTraceEnabled = previousState.secondBrainTraceEnabled;
|
||||
}
|
||||
if (previousState && typeof previousState.secondBrainTraceDebug === 'boolean') {
|
||||
secondBrainTraceDebug = previousState.secondBrainTraceDebug;
|
||||
}
|
||||
const initialGuardBtn = document.getElementById('designerGuardBtn');
|
||||
initialGuardBtn.classList.toggle('active', designerGuardEnabled);
|
||||
initialGuardBtn.setAttribute('data-tooltip', designerGuardEnabled ? 'Chronicle Guard Mode: On' : 'Chronicle Guard Mode: Off');
|
||||
const initialTraceBtn = document.getElementById('brainTraceBtn');
|
||||
initialTraceBtn.classList.toggle('active', secondBrainTraceEnabled);
|
||||
initialTraceBtn.setAttribute('data-tooltip', secondBrainTraceEnabled ? 'Second Brain Trace Mode: On' : 'Second Brain Trace Mode: Off');
|
||||
const initialTraceDebugBtn = document.getElementById('brainTraceDebugBtn');
|
||||
initialTraceDebugBtn.classList.toggle('active', secondBrainTraceDebug);
|
||||
initialTraceDebugBtn.setAttribute('data-tooltip', secondBrainTraceDebug ? 'Second Brain Debug JSON: On' : 'Second Brain Debug JSON: Off');
|
||||
|
||||
function fmt(text) { return marked.parse(text || ''); }
|
||||
|
||||
@@ -2211,6 +2888,39 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
vscode.postMessage({ type: 'getAgentContent', path: msg.selected });
|
||||
}
|
||||
break;
|
||||
case 'chronicleProjects':
|
||||
designerSel.innerHTML = '';
|
||||
msg.value.projects.forEach(p => {
|
||||
const o = document.createElement('option');
|
||||
o.value = p.id;
|
||||
o.innerText = p.name;
|
||||
o.title = p.recordRoot;
|
||||
if (p.id === msg.value.activeProjectId) o.selected = true;
|
||||
designerSel.appendChild(o);
|
||||
});
|
||||
const newDesignerOpt = document.createElement('option');
|
||||
newDesignerOpt.value = 'new';
|
||||
newDesignerOpt.innerText = '+ Add Designer Project...';
|
||||
designerSel.appendChild(newDesignerOpt);
|
||||
vscode.postMessage({ type: 'getChronicleRecords' });
|
||||
break;
|
||||
case 'chronicleRecords':
|
||||
chronicleRecordSel.innerHTML = '';
|
||||
if (!msg.value || msg.value.length === 0) {
|
||||
const emptyRecordOpt = document.createElement('option');
|
||||
emptyRecordOpt.value = '';
|
||||
emptyRecordOpt.innerText = 'No records yet';
|
||||
chronicleRecordSel.appendChild(emptyRecordOpt);
|
||||
break;
|
||||
}
|
||||
msg.value.forEach(record => {
|
||||
const o = document.createElement('option');
|
||||
o.value = record.path;
|
||||
o.innerText = record.relativePath;
|
||||
o.title = record.path;
|
||||
chronicleRecordSel.appendChild(o);
|
||||
});
|
||||
break;
|
||||
case 'agentContent':
|
||||
agentPrompt.value = msg.value;
|
||||
negativePrompt.value = msg.negativePrompt || '';
|
||||
@@ -2324,7 +3034,10 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
internet: internetEnabled,
|
||||
files: pendingFiles.length > 0 ? pendingFiles : undefined,
|
||||
agentFile: agentSel.value === 'none' ? undefined : agentSel.value,
|
||||
negativePrompt: negativePrompt.value.trim() || undefined
|
||||
negativePrompt: negativePrompt.value.trim() || undefined,
|
||||
designerGuard: designerGuardEnabled,
|
||||
secondBrainTrace: secondBrainTraceEnabled,
|
||||
secondBrainTraceDebug
|
||||
});
|
||||
input.value = ''; input.style.height = 'auto'; pendingFiles = []; renderAttachments();
|
||||
// 전송 완료 후 Draft State 리셋 + Stop 버튼 표시
|
||||
@@ -2370,6 +3083,27 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
document.getElementById('internetBtn').onclick = () => {
|
||||
internetEnabled = !internetEnabled; document.getElementById('internetBtn').classList.toggle('active', internetEnabled);
|
||||
};
|
||||
document.getElementById('designerGuardBtn').onclick = () => {
|
||||
designerGuardEnabled = !designerGuardEnabled;
|
||||
const btn = document.getElementById('designerGuardBtn');
|
||||
btn.classList.toggle('active', designerGuardEnabled);
|
||||
btn.setAttribute('data-tooltip', designerGuardEnabled ? 'Chronicle Guard Mode: On' : 'Chronicle Guard Mode: Off');
|
||||
saveUiState();
|
||||
};
|
||||
document.getElementById('brainTraceBtn').onclick = () => {
|
||||
secondBrainTraceEnabled = !secondBrainTraceEnabled;
|
||||
const btn = document.getElementById('brainTraceBtn');
|
||||
btn.classList.toggle('active', secondBrainTraceEnabled);
|
||||
btn.setAttribute('data-tooltip', secondBrainTraceEnabled ? 'Second Brain Trace Mode: On' : 'Second Brain Trace Mode: Off');
|
||||
saveUiState();
|
||||
};
|
||||
document.getElementById('brainTraceDebugBtn').onclick = () => {
|
||||
secondBrainTraceDebug = !secondBrainTraceDebug;
|
||||
const btn = document.getElementById('brainTraceDebugBtn');
|
||||
btn.classList.toggle('active', secondBrainTraceDebug);
|
||||
btn.setAttribute('data-tooltip', secondBrainTraceDebug ? 'Second Brain Debug JSON: On' : 'Second Brain Debug JSON: Off');
|
||||
saveUiState();
|
||||
};
|
||||
|
||||
let multiAgentEnabled = false;
|
||||
const setMultiAgentUi = (enabled) => {
|
||||
@@ -2427,6 +3161,15 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
}
|
||||
};
|
||||
|
||||
designerSel.onchange = () => {
|
||||
if (designerSel.value === 'new') {
|
||||
vscode.postMessage({ type: 'createChronicleProject' });
|
||||
} else {
|
||||
vscode.postMessage({ type: 'setChronicleProject', id: designerSel.value });
|
||||
vscode.postMessage({ type: 'getChronicleRecords' });
|
||||
}
|
||||
};
|
||||
|
||||
// Handle initial state and state updates from extension
|
||||
window.addEventListener('message', e => {
|
||||
const msg = e.data;
|
||||
@@ -2473,8 +3216,22 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
||||
vscode.postMessage({ type: 'deleteAgent', path: agentSel.value });
|
||||
};
|
||||
|
||||
document.getElementById('addDesignerBtn').onclick = () => vscode.postMessage({ type: 'createChronicleProject' });
|
||||
document.getElementById('openDesignerBtn').onclick = () => vscode.postMessage({ type: 'openChronicleFolder' });
|
||||
document.getElementById('writeChronicleBtn').onclick = () => vscode.postMessage({
|
||||
type: 'writeChronicleRecord',
|
||||
recordType: chronicleRecordTypeSel.value
|
||||
});
|
||||
document.getElementById('refreshChronicleRecordsBtn').onclick = () => vscode.postMessage({ type: 'getChronicleRecords' });
|
||||
document.getElementById('openChronicleRecordBtn').onclick = () => {
|
||||
if (!chronicleRecordSel.value) return;
|
||||
vscode.postMessage({ type: 'openChronicleRecord', path: chronicleRecordSel.value });
|
||||
};
|
||||
|
||||
vscode.postMessage({ type: 'getModels' });
|
||||
vscode.postMessage({ type: 'getAgents' });
|
||||
vscode.postMessage({ type: 'getChronicleProjects' });
|
||||
vscode.postMessage({ type: 'getChronicleRecords' });
|
||||
vscode.postMessage({ type: 'ready' });
|
||||
|
||||
// --- Proactive Behavioral Tracking ---
|
||||
|
||||
Reference in New Issue
Block a user