/**
* Conversation Preservation During Reload
*
* Task 1.3: Conversation Preservation During Reload
* Constraint: Reload breaks conversations → Conversation continuity guaranteed
*
* Witness Outcome: Active conversation + tool reload → conversation continues seamlessly
*
* Acceptance Criteria:
* - [ ] Conversation state serialized before reload
* - [ ] New tool class deserializes conversation state
* - [ ] Conversation ID preserved across reload
* - [ ] No data loss during migration
*/
import type { Tool, ToolIdentity } from './tool-interface.js';
export interface ToolPermission {
level: number; // 1=read, 2=write, 3=execute
upgradedAt: number;
inheritedFrom?: string; // M6: Parent tool name if permission was inherited
}
export interface ConversationState {
conversationId: string;
identity: {
toolName: string;
version: string;
capabilities: string[];
};
intentHistory: Array<{
action: string;
toolName?: string; // M5: Track which tool executed
alignment?: string;
timestamp: number;
}>;
permissionHistory: Array<{
toolName?: string; // M5: Per-tool grant
level: number;
scope: string; // M4: Always 'global'. Reserved for M5 (per-tool/per-resource scoping)
grantedAt: number;
}>;
currentLevel?: number; // M4: Deprecated in M5, use toolPermissions
toolPermissions?: Record<string, ToolPermission>; // M5: Per-tool permission levels
sharedContext?: any; // M5: Serialized SharedContext
}
interface ToolClass {
new(): Tool;
fromState?(state: ConversationState): Tool;
readonly identity: ToolIdentity;
}
export class Conversation {
public id: string;
public toolName: string;
private state: ConversationState;
private toolInstance: Tool | null = null;
constructor(id: string, toolName: string) {
this.id = id;
this.toolName = toolName;
this.state = {
conversationId: id,
identity: {
toolName,
version: '0.0.0',
capabilities: [],
},
intentHistory: [],
permissionHistory: [],
// M5: Initialize per-tool permissions (backward compatible with M4)
toolPermissions: {},
sharedContext: { resources: [] },
};
}
/**
* Migrate conversation to new tool class
*
* Preserves conversation state across hot-reload
*/
migrate(newToolClass: ToolClass): void {
const identity = newToolClass.identity;
console.error(`[Conversation] Migrating conversation ${this.id} to ${identity.name} v${identity.version}`);
// Serialize current state
const serializedState: ConversationState = {
conversationId: this.id,
identity: this.state.identity,
intentHistory: [...this.state.intentHistory],
permissionHistory: [...this.state.permissionHistory],
currentLevel: this.state.currentLevel, // M4: Backward compatibility
toolPermissions: this.state.toolPermissions || {}, // M5: Per-tool permissions
sharedContext: this.state.sharedContext || { resources: [] }, // M5: Shared resources
};
// Create new tool instance with preserved state
if (newToolClass.fromState) {
this.toolInstance = newToolClass.fromState(serializedState);
} else {
// Fallback: create fresh instance
console.warn(`[Conversation] Tool ${identity.name} does not support state migration`);
this.toolInstance = new newToolClass();
}
// Update identity to reflect new version
this.state.identity = {
toolName: identity.name,
version: identity.version,
capabilities: identity.capabilities,
};
console.error(`[Conversation] Migration complete: ${this.id}`);
}
/**
* Get conversation state (for persistence)
*/
getState(): ConversationState {
return { ...this.state };
}
/**
* Restore conversation state (for resumption)
*/
setState(state: ConversationState): void {
this.state = state;
}
}