chore(integration): snapshot local main worktree

This commit is contained in:
inman
2026-08-31 10:23:21 +08:00
parent 48a9189939
commit 33fb31fb28
116 changed files with 8108 additions and 3026 deletions

View File

@@ -32,7 +32,6 @@ export interface CodingProjectConfig {
initialized: boolean;
agents: CodingProjectAgent[];
knowledgeDirectory: 'knowledge';
legacyConversationNotice: 'none' | 'pending' | 'acknowledged';
createdAt: string;
updatedAt: string;
}

View File

@@ -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)) {

View File

@@ -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;
}

View File

@@ -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,
);
}