Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ const nextConfig = {
"tls-client-node",
"koffi",
"tough-cookie",
"@ngrok/ngrok",
"child_process",
"fs",
"path",
Expand Down Expand Up @@ -100,6 +101,12 @@ const nextConfig = {
contextRegExp: /thread-stream/,
})
);

// Mark @ngrok/ngrok as external to prevent webpack from trying to bundle its .node binaries
config.externals = config.externals || [];
config.externals.push({
"@ngrok/ngrok": "commonjs @ngrok/ngrok",
});
// ── Turbopack / Next.js 16 module-hash patch (#394, #396, #398) ────────
//
// Next.js 16 (with or without Turbopack) compiles the instrumentation hook
Expand Down
7 changes: 3 additions & 4 deletions open-sse/services/antigravityHeaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ export const GEMINI_CLI_VERSION = "0.39.1";
export const GEMINI_SDK_VERSION = "1.30.0";
export const NODE_VERSION = "v22.21.1";
export const ANTIGRAVITY_LOAD_CODE_ASSIST_USER_AGENT = "google-api-nodejs-client/10.3.0";
export const ANTIGRAVITY_LOAD_CODE_ASSIST_API_CLIENT = "google-cloud-sdk vscode_cloudshelleditor/0.1";
export const ANTIGRAVITY_LOAD_CODE_ASSIST_API_CLIENT =
"google-cloud-sdk vscode_cloudshelleditor/0.1";
const LOAD_CODE_ASSIST_METADATA = Object.freeze({
ideType: "IDE_UNSPECIFIED",
platform: "PLATFORM_UNSPECIFIED",
Expand Down Expand Up @@ -133,6 +134,4 @@ export function googApiClientHeader(): string {
return `google-genai-sdk/${GEMINI_SDK_VERSION} gl-node/${NODE_VERSION}`;
}

export {
ANTIGRAVITY_VERSION,
};
export { ANTIGRAVITY_VERSION };
12 changes: 0 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 21 additions & 17 deletions src/app/(dashboard)/dashboard/memory/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,17 +205,21 @@ export default function MemoryPage() {
{health !== null && (
<span
className={`inline-block w-3 h-3 rounded-full ${health.working ? "bg-green-500" : "bg-red-500"}`}
title={health.working ? `Pipeline OK (${health.latencyMs}ms)` : "Pipeline error"}
title={
health.working
? t("pipelineOk", { latencyMs: health.latencyMs })
: t("pipelineError")
}
/>
)}
{health === null && !checkingHealth && (
<span
className="inline-block w-3 h-3 rounded-full bg-gray-400"
title="Health unknown"
title={t("healthUnknown")}
/>
)}
<Button variant="outline" size="sm" onClick={checkHealth} disabled={checkingHealth}>
{checkingHealth ? "Checking..." : "Check Health"}
{checkingHealth ? t("checkingHealth") : t("checkHealth")}
</Button>
</div>
</div>
Expand Down Expand Up @@ -321,7 +325,7 @@ export default function MemoryPage() {

<div className="flex items-center justify-between mt-4">
<div className="text-sm text-gray-500">
Page {page} of {totalPages} ({total} total)
{t("pageInfo", { page, totalPages, total })}
</div>
<div className="flex gap-2">
<Button
Expand All @@ -330,15 +334,15 @@ export default function MemoryPage() {
disabled={page === 1}
onClick={() => setPage((p) => Math.max(1, p - 1))}
>
Previous
{t("previous")}
</Button>
<Button
variant="outline"
size="sm"
disabled={page >= totalPages}
onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
>
Next
{t("next")}
</Button>
</div>
</div>
Expand All @@ -356,47 +360,47 @@ export default function MemoryPage() {
onClick={() => setAddDialogOpen(false)}
disabled={isSubmitting}
>
Cancel
{t("cancel")}
</Button>
<Button
onClick={handleAddMemory}
loading={isSubmitting}
disabled={!newMemory.key || !newMemory.content}
>
Save
{t("save")}
</Button>
</>
}
>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium mb-1">Type</label>
<label className="block text-sm font-medium mb-1">{t("type")}</label>
<Select
value={newMemory.type}
onChange={(e) => setNewMemory({ ...newMemory, type: e.target.value as any })}
className="w-full"
>
<option value="factual">Factual</option>
<option value="episodic">Episodic</option>
<option value="procedural">Procedural</option>
<option value="semantic">Semantic</option>
<option value="factual">{t("factual")}</option>
<option value="episodic">{t("episodic")}</option>
<option value="procedural">{t("procedural")}</option>
<option value="semantic">{t("semantic")}</option>
</Select>
</div>
<div>
<label className="block text-sm font-medium mb-1">Key</label>
<label className="block text-sm font-medium mb-1">{t("key")}</label>
<Input
value={newMemory.key}
onChange={(e) => setNewMemory({ ...newMemory, key: e.target.value })}
placeholder="e.g., user_preference_theme"
placeholder={t("keyPlaceholder")}
className="w-full"
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">Content</label>
<label className="block text-sm font-medium mb-1">{t("content")}</label>
<Input
value={newMemory.content}
onChange={(e) => setNewMemory({ ...newMemory, content: e.target.value })}
placeholder="e.g., Prefers dark mode"
placeholder={t("contentPlaceholder")}
className="w-full"
/>
</div>
Expand Down
31 changes: 23 additions & 8 deletions src/i18n/messages/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -1335,13 +1335,17 @@
}
}
},
"autoConfiguredTab": "Auto Configured Tab",
"toolCategoriesDesc": "Tool Categories Desc",
"allToolsTab": "All Tools Tab",
"guidedClientsTab": "Guided Clients Tab",
"mitmClientsTab": "Mitm Clients Tab",
"toolCategories": "Tool Categories",
"visibleToolsCount": "Visible Tools Count"
"autoConfiguredTab": "自动配置",
"toolCategoriesDesc": "配置 AI 编程助手通过 OmniRoute 路由",
"allToolsTab": "所有工具",
"guidedClientsTab": "引导客户端",
"mitmClientsTab": "MITM 客户端",
"customCliTab": "自定义 CLI",
"toolCategories": "工具分类",
"visibleToolsCount": "{count} 个工具可用",
"installationGuide": "安装指南",
"whenToUseLabel": "何时使用",
"openToolDocs": "打开工具文档"
},
"combos": {
"title": "组合",
Expand Down Expand Up @@ -4324,15 +4328,26 @@
"addMemory": "添加记忆",
"type": "类型",
"key": "键",
"keyPlaceholder": "例如:user_preference_theme",
"content": "内容",
"contentPlaceholder": "例如:偏好深色模式",
"created": "创建时间",
"actions": "操作",
"delete": "删除",
"factual": "事实型",
"episodic": "情景型",
"procedural": "程序型",
"semantic": "语义型",
"a": "A"
"previous": "上一页",
"next": "下一页",
"pageInfo": "第 {page} 页,共 {totalPages} 页(共 {total} 条)",
"checkingHealth": "检查中...",
"checkHealth": "检查健康",
"pipelineOk": "Pipeline 正常 ({latencyMs}ms)",
"pipelineError": "Pipeline 错误",
"healthUnknown": "健康状态未知",
"cancel": "取消",
"save": "保存"
},
"skills": {
"title": "技能",
Expand Down
67 changes: 65 additions & 2 deletions src/lib/memory/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,78 @@ function rowToMemory(row: MemoryRow): Memory {
}

/**
* Create a new memory entry
* Find existing memory by apiKeyId and key (for UPSERT logic)
*/
function findExistingMemory(
db: ReturnType<typeof getDbInstance>,
apiKeyId: string,
key: string
): MemoryRow | undefined {
if (!key) return undefined;
const stmt = db.prepare(
"SELECT * FROM memories WHERE api_key_id = ? AND key = ? ORDER BY created_at DESC LIMIT 1"
);
return stmt.get(apiKeyId, key) as MemoryRow | undefined;
}

/**
* Create a new memory entry (UPSERT: updates existing if same apiKeyId + key)
*/
export async function createMemory(
memory: Omit<Memory, "id" | "createdAt" | "updatedAt">
): Promise<Memory> {
const db = getDbInstance();
const id = crypto.randomUUID();
const now = new Date().toISOString();

// Check for existing memory with same apiKeyId + key (UPSERT logic)
const existing = memory.key ? findExistingMemory(db, memory.apiKeyId, memory.key) : undefined;

if (existing) {
// UPDATE existing record
const updatedMetadata = { ...parseJSON(existing.metadata), ...memory.metadata };
const stmt = db.prepare(
"UPDATE memories SET content = ?, metadata = ?, updated_at = ?, session_id = ?, type = ?, expires_at = ? WHERE id = ?"
);
stmt.run(
memory.content,
JSON.stringify(updatedMetadata),
now,
memory.sessionId,
memory.type,
memory.expiresAt ?? null,
existing.id
);
Comment on lines +109 to +120

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The UPDATE statement in the UPSERT logic is missing the expires_at field. This means that when an existing memory entry is updated, its expiration time will not be updated in the database, even though the returned updatedMemory object reflects the new value. This leads to a state mismatch between the application and the database.

Suggested change
const stmt = db.prepare(
"UPDATE memories SET content = ?, metadata = ?, updated_at = ?, session_id = ?, type = ? WHERE id = ?"
);
stmt.run(
memory.content,
JSON.stringify(updatedMetadata),
now,
memory.sessionId,
memory.type,
existing.id
);
const stmt = db.prepare(
"UPDATE memories SET content = ?, metadata = ?, updated_at = ?, session_id = ?, type = ?, expires_at = ? WHERE id = ?"
);
stmt.run(
memory.content,
JSON.stringify(updatedMetadata),
now,
memory.sessionId,
memory.type,
memory.expiresAt instanceof Date ? memory.expiresAt.toISOString() : (memory.expiresAt ?? null),
existing.id
);


const updatedMemory: Memory = {
id: String(existing.id),
apiKeyId: memory.apiKeyId,
sessionId: memory.sessionId,
type: memory.type,
key: memory.key,
content: memory.content,
metadata: updatedMetadata,
createdAt: new Date(String(existing.created_at)),
updatedAt: new Date(now),
expiresAt: memory.expiresAt ?? null,
};

// Invalidate and update cache
invalidateMemoryCache(existing.id);
evictIfNeeded(_memoryCache);
_memoryCache.set(existing.id, { value: updatedMemory, timestamp: Date.now() });

log.info("memory.updated", {
apiKeyId: memory.apiKeyId,
type: memory.type,
id: existing.id,
key: memory.key,
});

return updatedMemory;
}

// INSERT new record if not exists
const id = crypto.randomUUID();
const stmt = db.prepare(
"INSERT INTO memories (id, api_key_id, session_id, type, key, content, metadata, created_at, updated_at, expires_at) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
Expand Down
Loading