chore(integration): snapshot local main worktree
This commit is contained in:
@@ -32,7 +32,6 @@ export interface CodingProjectConfig {
|
||||
initialized: boolean;
|
||||
agents: CodingProjectAgent[];
|
||||
knowledgeDirectory: 'knowledge';
|
||||
legacyConversationNotice: 'none' | 'pending' | 'acknowledged';
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@ export interface ImportedPiModelProfile {
|
||||
reasoning: boolean;
|
||||
thinkingLevelMap?: Partial<Record<ImportedThinkingLevel, string | null>>;
|
||||
compat?: {
|
||||
thinkingFormat?: 'deepseek' | 'openrouter';
|
||||
thinkingFormat?: 'deepseek' | 'openrouter' | 'qwen';
|
||||
supportsDeveloperRole?: boolean;
|
||||
supportsReasoningEffort?: boolean;
|
||||
supportsStore?: boolean;
|
||||
requiresReasoningContentOnAssistantMessages?: boolean;
|
||||
};
|
||||
}
|
||||
@@ -24,9 +27,12 @@ export interface ImportedModelProfile {
|
||||
pi?: ImportedPiModelProfile;
|
||||
}
|
||||
|
||||
const VERIFIED_QWEN_PLUS_MODEL_IDS = new Set([
|
||||
const VERIFIED_QWEN_36_PLUS_MODEL_IDS = new Set([
|
||||
'qwen3.6-plus',
|
||||
'qwen3.6-plus-2026-04-02',
|
||||
]);
|
||||
const VERIFIED_QWEN_PLUS_MODEL_IDS = new Set([
|
||||
...VERIFIED_QWEN_36_PLUS_MODEL_IDS,
|
||||
'qwen3.7-plus',
|
||||
'qwen3.7-plus-2026-05-26',
|
||||
]);
|
||||
@@ -42,7 +48,8 @@ function imageModalities(): ImportedModelProfile['modalities'] {
|
||||
|
||||
export function getImportedModelProfile(rawModelId: string): ImportedModelProfile | null {
|
||||
const modelId = rawModelId.trim();
|
||||
if (modelId.toLowerCase() === 'deepseek-v4-pro') {
|
||||
const normalizedModelId = modelId.toLowerCase();
|
||||
if (normalizedModelId === 'deepseek-v4-pro') {
|
||||
return {
|
||||
modalities: {
|
||||
input: ['text'],
|
||||
@@ -68,7 +75,31 @@ export function getImportedModelProfile(rawModelId: string): ImportedModelProfil
|
||||
},
|
||||
};
|
||||
}
|
||||
if (VERIFIED_QWEN_PLUS_MODEL_IDS.has(modelId.toLowerCase())) {
|
||||
if (normalizedModelId === 'qwen3.8-max') {
|
||||
return {
|
||||
modalities: imageModalities(),
|
||||
limit: {
|
||||
context: 1_000_000,
|
||||
output: 131_072,
|
||||
},
|
||||
pi: {
|
||||
reasoning: true,
|
||||
thinkingLevelMap: {
|
||||
minimal: null,
|
||||
low: 'low',
|
||||
medium: 'medium',
|
||||
high: 'xhigh',
|
||||
},
|
||||
compat: {
|
||||
thinkingFormat: 'qwen',
|
||||
supportsDeveloperRole: false,
|
||||
supportsReasoningEffort: true,
|
||||
supportsStore: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
if (VERIFIED_QWEN_PLUS_MODEL_IDS.has(normalizedModelId)) {
|
||||
return {
|
||||
modalities: imageModalities(),
|
||||
limit: {
|
||||
@@ -76,6 +107,25 @@ export function getImportedModelProfile(rawModelId: string): ImportedModelProfil
|
||||
output: 65_536,
|
||||
},
|
||||
visionTokenEstimator: 'qwen-32px-grid',
|
||||
...(VERIFIED_QWEN_36_PLUS_MODEL_IDS.has(normalizedModelId)
|
||||
? {
|
||||
pi: {
|
||||
reasoning: true,
|
||||
thinkingLevelMap: {
|
||||
minimal: null,
|
||||
low: null,
|
||||
medium: null,
|
||||
high: 'high',
|
||||
},
|
||||
compat: {
|
||||
thinkingFormat: 'qwen' as const,
|
||||
supportsDeveloperRole: false,
|
||||
supportsReasoningEffort: false,
|
||||
supportsStore: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
if (QWEN_VL_PATTERN.test(modelId) || QWEN_OMNI_PATTERN.test(modelId)) {
|
||||
|
||||
@@ -16,24 +16,6 @@ export type ProjectAgentResponsibility = {
|
||||
principles: string[];
|
||||
};
|
||||
|
||||
export type ProjectAgentConfig = {
|
||||
id: string;
|
||||
avatarId: string;
|
||||
avatarDataUrl?: string;
|
||||
roleName: string;
|
||||
name: string;
|
||||
builtIn: boolean;
|
||||
enabled: boolean;
|
||||
model: string | null;
|
||||
skillIds: string[];
|
||||
responsibility: ProjectAgentResponsibility;
|
||||
prompt: string;
|
||||
archivedAt?: string | null;
|
||||
pinned?: boolean;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
};
|
||||
|
||||
export const MAX_PROJECT_AGENT_AVATAR_DATA_URL_LENGTH = 800_000;
|
||||
|
||||
const PROJECT_AGENT_AVATAR_DATA_URL_PATTERN = /^data:image\/(?:webp|png|jpeg);base64,[A-Za-z0-9+/]+={0,2}$/u;
|
||||
@@ -43,60 +25,3 @@ export function isProjectAgentAvatarDataUrl(value: unknown): value is string {
|
||||
&& value.length <= MAX_PROJECT_AGENT_AVATAR_DATA_URL_LENGTH
|
||||
&& PROJECT_AGENT_AVATAR_DATA_URL_PATTERN.test(value);
|
||||
}
|
||||
|
||||
export type ProjectConfig = {
|
||||
schemaVersion: 1;
|
||||
projectType: ProjectType;
|
||||
initialized: boolean;
|
||||
defaultModel: string | null;
|
||||
agents: ProjectAgentConfig[];
|
||||
knowledgeDirectory: 'knowledge';
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export function createProjectConfig(
|
||||
now = new Date().toISOString(),
|
||||
projectType: ProjectType = 'custom',
|
||||
): ProjectConfig {
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
projectType,
|
||||
initialized: false,
|
||||
defaultModel: null,
|
||||
agents: [],
|
||||
knowledgeDirectory: 'knowledge',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
}
|
||||
|
||||
export function validateAgentNames(agents: ProjectAgentConfig[]): string[] {
|
||||
const errors: string[] = [];
|
||||
const names = new Set<string>();
|
||||
for (const item of agents) {
|
||||
const name = item.name.trim();
|
||||
if (!name) errors.push(`${item.id}:name-required`);
|
||||
if (name.length > 30) errors.push(`${item.id}:name-too-long`);
|
||||
const key = name.toLocaleLowerCase();
|
||||
if (key && names.has(key)) errors.push(`${item.id}:name-duplicate`);
|
||||
names.add(key);
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
export function validateAgentConfigs(agents: ProjectAgentConfig[]): string[] {
|
||||
const errors = validateAgentNames(agents);
|
||||
for (const agent of agents) {
|
||||
if (!/^avatar-(0[1-9]|1[0-6])$/.test(agent.avatarId)) {
|
||||
errors.push(`${agent.id}:avatar-required`);
|
||||
}
|
||||
if (!agent.model?.trim()) {
|
||||
errors.push(`${agent.id}:model-required`);
|
||||
}
|
||||
if (!agent.responsibility.mission.trim()) {
|
||||
errors.push(`${agent.id}:responsibility-required`);
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
export type ProjectSessionMetadata = {
|
||||
sessionId: string;
|
||||
agentId: string;
|
||||
archivedAt: string | null;
|
||||
unreadCount: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type ProjectConversationState = {
|
||||
schemaVersion: 1;
|
||||
sessions: ProjectSessionMetadata[];
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
function cleanString(value: unknown): string {
|
||||
return typeof value === 'string' ? value.trim() : '';
|
||||
}
|
||||
|
||||
function normalizeSession(value: unknown): ProjectSessionMetadata | null {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
||||
const record = value as Partial<ProjectSessionMetadata>;
|
||||
const sessionId = cleanString(record.sessionId);
|
||||
const agentId = cleanString(record.agentId);
|
||||
if (!sessionId || !/^[a-z0-9][a-z0-9_-]{0,127}$/i.test(sessionId) || !agentId) return null;
|
||||
const now = new Date().toISOString();
|
||||
const createdAt = cleanString(record.createdAt) || now;
|
||||
const updatedAt = cleanString(record.updatedAt) || createdAt;
|
||||
const unreadCount = typeof record.unreadCount === 'number' && Number.isFinite(record.unreadCount)
|
||||
? Math.max(0, Math.floor(record.unreadCount))
|
||||
: 0;
|
||||
return {
|
||||
sessionId,
|
||||
agentId,
|
||||
archivedAt: cleanString(record.archivedAt) || null,
|
||||
unreadCount,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
export function createProjectConversationState(now = new Date().toISOString()): ProjectConversationState {
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
sessions: [],
|
||||
updatedAt: now,
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeProjectConversationState(value: unknown): ProjectConversationState {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||||
throw new Error('Project conversation state must be an object');
|
||||
}
|
||||
const record = value as Partial<ProjectConversationState>;
|
||||
if (record.schemaVersion !== 1) throw new Error('Unsupported project conversation state schema');
|
||||
const sessions = Array.isArray(record.sessions)
|
||||
? record.sessions.map(normalizeSession).filter((item): item is ProjectSessionMetadata => Boolean(item))
|
||||
: [];
|
||||
const deduped = new Map<string, ProjectSessionMetadata>();
|
||||
for (const session of sessions) deduped.set(session.sessionId, session);
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
sessions: [...deduped.values()],
|
||||
updatedAt: cleanString(record.updatedAt) || new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
export function upsertProjectSessionMetadata(
|
||||
state: ProjectConversationState,
|
||||
sessionId: string,
|
||||
agentId: string,
|
||||
now = new Date().toISOString(),
|
||||
): ProjectConversationState {
|
||||
const current = state.sessions.find((item) => item.sessionId === sessionId);
|
||||
const next: ProjectSessionMetadata = {
|
||||
sessionId,
|
||||
agentId,
|
||||
archivedAt: current?.archivedAt ?? null,
|
||||
unreadCount: current?.unreadCount ?? 0,
|
||||
createdAt: current?.createdAt ?? now,
|
||||
updatedAt: now,
|
||||
};
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
sessions: [next, ...state.sessions.filter((item) => item.sessionId !== sessionId)],
|
||||
updatedAt: now,
|
||||
};
|
||||
}
|
||||
|
||||
export function patchProjectSessionMetadata(
|
||||
state: ProjectConversationState,
|
||||
sessionId: string,
|
||||
patch: Partial<Pick<ProjectSessionMetadata, 'agentId' | 'archivedAt' | 'unreadCount'>>,
|
||||
now = new Date().toISOString(),
|
||||
): ProjectConversationState {
|
||||
const sessions = state.sessions.map((item) => item.sessionId === sessionId
|
||||
? {
|
||||
...item,
|
||||
...(patch.agentId ? { agentId: patch.agentId } : {}),
|
||||
...(patch.archivedAt !== undefined ? { archivedAt: patch.archivedAt } : {}),
|
||||
...(patch.unreadCount !== undefined ? { unreadCount: Math.max(0, Math.floor(patch.unreadCount)) } : {}),
|
||||
updatedAt: now,
|
||||
}
|
||||
: item);
|
||||
return { schemaVersion: 1, sessions, updatedAt: now };
|
||||
}
|
||||
|
||||
/**
|
||||
* Reading a session changes its unread state, not its activity timestamp.
|
||||
* Keeping those concerns separate prevents a navigation click from moving an
|
||||
* Agent in a list that is ordered by the latest conversation activity.
|
||||
*/
|
||||
export function markProjectSessionRead(
|
||||
state: ProjectConversationState,
|
||||
sessionId: string,
|
||||
now = new Date().toISOString(),
|
||||
): ProjectConversationState {
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
sessions: state.sessions.map((item) => item.sessionId === sessionId
|
||||
? { ...item, unreadCount: 0 }
|
||||
: item),
|
||||
updatedAt: now,
|
||||
};
|
||||
}
|
||||
|
||||
export function removeProjectSessionMetadata(
|
||||
state: ProjectConversationState,
|
||||
sessionId: string,
|
||||
now = new Date().toISOString(),
|
||||
): ProjectConversationState {
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
sessions: state.sessions.filter((item) => item.sessionId !== sessionId),
|
||||
updatedAt: now,
|
||||
};
|
||||
}
|
||||
|
||||
export function completeProjectSession(
|
||||
state: ProjectConversationState,
|
||||
sessionId: string,
|
||||
incrementUnread: boolean,
|
||||
now = new Date().toISOString(),
|
||||
): ProjectConversationState {
|
||||
const current = state.sessions.find((item) => item.sessionId === sessionId);
|
||||
if (!current) return state;
|
||||
return patchProjectSessionMetadata(
|
||||
state,
|
||||
sessionId,
|
||||
{ unreadCount: current.unreadCount + (incrementUnread ? 1 : 0) },
|
||||
now,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user