Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f79770cdb0 | ||
|
|
9ba602ffd2 | ||
|
|
842fbe6044 |
No files matched your search
+11
-2
@@ -16,11 +16,20 @@ Hy Memory and MemHub are versioned and released independently.
|
|||||||
Because PyPI release metadata is immutable, the next SDK patch release must
|
Because PyPI release metadata is immutable, the next SDK patch release must
|
||||||
carry the updated license metadata; `2.0.1` retains its published metadata.
|
carry the updated license metadata; `2.0.1` retains its published metadata.
|
||||||
|
|
||||||
## MemHub 1.0.1 — Unreleased
|
## MemHub 1.0.2 — Unreleased
|
||||||
|
|
||||||
- Updated the source version to `1.0.1` (build 5).
|
- Updated the source version to `1.0.2` (build 6).
|
||||||
- Pinned the public SDK to `hy-memory==2.0.2`.
|
- Pinned the public SDK to `hy-memory==2.0.2`.
|
||||||
- Pinned `onnxruntime==1.23.2` to retain macOS x64 packaging support.
|
- Pinned `onnxruntime==1.23.2` to retain macOS x64 packaging support.
|
||||||
|
- Fixed relaunch and upgrade failures when a prior managed Runtime exists.
|
||||||
|
- Embedded canonical build identity when an external packaging pipeline omits
|
||||||
|
release environment fields.
|
||||||
|
- Added automatic Linux Runtime upload to tagged Draft Releases.
|
||||||
|
|
||||||
|
## MemHub 1.0.1 — Superseded before release
|
||||||
|
|
||||||
|
- The candidate tag was not published as a GitHub Release; `1.0.2` replaces it
|
||||||
|
after the managed-Runtime relaunch fix.
|
||||||
|
|
||||||
## MemHub 1.0.0
|
## MemHub 1.0.0
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -6,13 +6,13 @@ SDK 和记忆引擎。One Memory / `one-memory` 是历史模块、数据目录
|
|||||||
|
|
||||||
## 发行与平台合同
|
## 发行与平台合同
|
||||||
|
|
||||||
公共 Python SDK 为 `hy-memory==2.0.2`。MemHub 源码版本为 `1.0.1`
|
公共 Python SDK 为 `hy-memory==2.0.2`。MemHub 源码版本为 `1.0.2`
|
||||||
(build 5),GitHub 仓库为
|
(build 6),GitHub 仓库为
|
||||||
[`Tencent-Hunyuan/Hy-Memory`](https://github.com/Tencent-Hunyuan/Hy-Memory),npm 包名为
|
[`Tencent-Hunyuan/Hy-Memory`](https://github.com/Tencent-Hunyuan/Hy-Memory),npm 包名为
|
||||||
`memhub-cli`。当前代码架构见
|
`memhub-cli`。当前代码架构见
|
||||||
[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md)。
|
[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md)。
|
||||||
|
|
||||||
`v1.0.1` 的发行目标是 Linux x64/arm64 Runtime 与 macOS arm64/x64 App 和 Runtime。
|
`v1.0.2` 的发行目标是 Linux x64/arm64 Runtime 与 macOS arm64/x64 App 和 Runtime。
|
||||||
Linux 制品由 GitHub Actions 在原生架构 runner 上构建并完成生命周期验收;macOS
|
Linux 制品由 GitHub Actions 在原生架构 runner 上构建并完成生命周期验收;macOS
|
||||||
制品由发行流水线完成 Developer ID 签名、公证和验收后手动上传。Windows 不支持。
|
制品由发行流水线完成 Developer ID 签名、公证和验收后手动上传。Windows 不支持。
|
||||||
只有同时出现在 GitHub Release 与 `release-manifest.json` 中、且通过公开校验的制品才属于
|
只有同时出现在 GitHub Release 与 `release-manifest.json` 中、且通过公开校验的制品才属于
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@one-memory/cli",
|
"name": "@one-memory/cli",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"description": "MemHub bootstrap CLI for installing and managing the local-first shared memory runtime",
|
"description": "MemHub bootstrap CLI for installing and managing the local-first shared memory runtime",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "one-memory"
|
name = "one-memory"
|
||||||
version = "1.0.1"
|
version = "1.0.2"
|
||||||
description = "MemHub local-first memory core"
|
description = "MemHub local-first memory core"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
|||||||
Generated
+1
-1
@@ -1801,7 +1801,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "one-memory"
|
name = "one-memory"
|
||||||
version = "1.0.1"
|
version = "1.0.2"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "aiomysql" },
|
{ name = "aiomysql" },
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@one-memory/desktop",
|
"name": "@one-memory/desktop",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "dist/main.js",
|
"main": "dist/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { readFile } from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
|
import { expect, it } from "vitest";
|
||||||
|
|
||||||
|
it("bakes fallback release identity when the packaging pipeline omits it", async () => {
|
||||||
|
const config = await readFile(
|
||||||
|
path.resolve(__dirname, "../tsup.config.ts"),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(config).toContain("resolveBuildNumber");
|
||||||
|
expect(config).toContain(
|
||||||
|
"const releaseBuildNumber = resolveBuildNumber(process.env)",
|
||||||
|
);
|
||||||
|
expect(config).toMatch(
|
||||||
|
/__ONE_MEMORY_BUILD_NUMBER__: JSON\.stringify\(\s*String\(releaseBuildNumber\)/,
|
||||||
|
);
|
||||||
|
expect(config).toMatch(
|
||||||
|
/process\.env\.ONE_MEMORY_COMMIT\?\.trim\(\) \|\| readSourceCommit\(\)/,
|
||||||
|
);
|
||||||
|
expect(config).toMatch(
|
||||||
|
/execFileSync\("git", \["rev-parse", "HEAD"\],[\s\S]*?encoding: "utf8"/,
|
||||||
|
);
|
||||||
|
expect(config).toContain(
|
||||||
|
"__ONE_MEMORY_COMMIT__: JSON.stringify(releaseCommit)",
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -234,7 +234,9 @@ describe("desktop lifecycle failure paths", () => {
|
|||||||
await f.flush();
|
await f.flush();
|
||||||
expect(f.createWindow).not.toHaveBeenCalled();
|
expect(f.createWindow).not.toHaveBeenCalled();
|
||||||
expect(f.quit().preventDefault).toHaveBeenCalled();
|
expect(f.quit().preventDefault).toHaveBeenCalled();
|
||||||
f.stopping.resolve();
|
// Teardown can fail after the startup error (for example, while writing a
|
||||||
|
// diagnostic). It must not replace the useful error with a second dialog.
|
||||||
|
f.stopping.reject(new Error("diagnostic_detail_key_invalid"));
|
||||||
await f.flush();
|
await f.flush();
|
||||||
// Whatever did come up before the failure still has to be torn down.
|
// Whatever did come up before the failure still has to be torn down.
|
||||||
expect(f.stop).toHaveBeenCalledTimes(1);
|
expect(f.stop).toHaveBeenCalledTimes(1);
|
||||||
|
|||||||
@@ -108,7 +108,9 @@ export function startDesktopLifecycle(options: LifecycleOptions): void {
|
|||||||
shutdown = startup
|
shutdown = startup
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
.then(options.stop)
|
.then(options.stop)
|
||||||
.catch(options.onError)
|
// Quit-time failures are logged by stop itself. A second modal here can
|
||||||
|
// only obscure the startup error that initiated this shutdown.
|
||||||
|
.catch(report)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
stopped = true;
|
stopped = true;
|
||||||
app.quit();
|
app.quit();
|
||||||
|
|||||||
@@ -124,9 +124,10 @@ export function hourlyRuntimeInstanceId(): string {
|
|||||||
* `build_number` is the only ordering authority the installer compares, so a
|
* `build_number` is the only ordering authority the installer compares, so a
|
||||||
* package that omits it defaults to 0 and can never outrank an earlier install:
|
* package that omits it defaults to 0 and can never outrank an earlier install:
|
||||||
* R->R.1 would be refused as a downgrade. The value is baked into the bundle's
|
* R->R.1 would be refused as a downgrade. The value is baked into the bundle's
|
||||||
* `LSEnvironment` at pack time (`apps/desktop/scripts/after-pack.cjs`); this
|
* `LSEnvironment` at pack time (`apps/desktop/scripts/after-pack.cjs`) and is
|
||||||
* reads it back and never invents one, so a source checkout that did not pass a
|
* also compiled from the canonical root release contract. This reader accepts
|
||||||
* build number stays undefined rather than fabricating order.
|
* either source so Finder launches still carry ordering even when an external
|
||||||
|
* packaging pipeline omitted the plist field.
|
||||||
*/
|
*/
|
||||||
export function resolvePackagedBuildNumber(
|
export function resolvePackagedBuildNumber(
|
||||||
environmentValue: unknown,
|
environmentValue: unknown,
|
||||||
|
|||||||
@@ -1404,7 +1404,7 @@ if (desktopVersion !== null) {
|
|||||||
component: "desktop.lifecycle",
|
component: "desktop.lifecycle",
|
||||||
event: "desktop.runtime_stop",
|
event: "desktop.runtime_stop",
|
||||||
outcome: event.outcome,
|
outcome: event.outcome,
|
||||||
details: { runtime: event.detail },
|
details: { runtime_state: event.detail },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,29 @@
|
|||||||
|
import { execFileSync } from "node:child_process";
|
||||||
import { createRequire } from "node:module";
|
import { createRequire } from "node:module";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
import { defineConfig } from "tsup";
|
import { defineConfig } from "tsup";
|
||||||
|
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
const { resolveBuildVersion } =
|
const { resolveBuildNumber, resolveBuildVersion } =
|
||||||
require("../../scripts/release-version.cjs") as {
|
require("../../scripts/release-version.cjs") as {
|
||||||
|
resolveBuildNumber(environment?: NodeJS.ProcessEnv, root?: string): number;
|
||||||
resolveBuildVersion(environment?: NodeJS.ProcessEnv, root?: string): string;
|
resolveBuildVersion(environment?: NodeJS.ProcessEnv, root?: string): string;
|
||||||
};
|
};
|
||||||
const releaseVersion = resolveBuildVersion(process.env);
|
const releaseVersion = resolveBuildVersion(process.env);
|
||||||
|
const releaseBuildNumber = resolveBuildNumber(process.env);
|
||||||
|
const releaseCommit =
|
||||||
|
process.env.ONE_MEMORY_COMMIT?.trim() || readSourceCommit();
|
||||||
|
|
||||||
|
function readSourceCommit(): string {
|
||||||
|
try {
|
||||||
|
return execFileSync("git", ["rev-parse", "HEAD"], {
|
||||||
|
cwd: fileURLToPath(new URL("../..", import.meta.url)),
|
||||||
|
encoding: "utf8",
|
||||||
|
}).trim();
|
||||||
|
} catch {
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
entry: ["src/main.ts", "src/preload.ts"],
|
entry: ["src/main.ts", "src/preload.ts"],
|
||||||
@@ -27,11 +44,7 @@ export default defineConfig({
|
|||||||
__ONE_MEMORY_TRIAL_PROFILE__: JSON.stringify(
|
__ONE_MEMORY_TRIAL_PROFILE__: JSON.stringify(
|
||||||
process.env.ONE_MEMORY_BUILD_TRIAL_PROFILE ?? null,
|
process.env.ONE_MEMORY_BUILD_TRIAL_PROFILE ?? null,
|
||||||
),
|
),
|
||||||
__ONE_MEMORY_BUILD_NUMBER__: JSON.stringify(
|
__ONE_MEMORY_BUILD_NUMBER__: JSON.stringify(String(releaseBuildNumber)),
|
||||||
process.env.ONE_MEMORY_BUILD_NUMBER ?? "unknown",
|
__ONE_MEMORY_COMMIT__: JSON.stringify(releaseCommit),
|
||||||
),
|
|
||||||
__ONE_MEMORY_COMMIT__: JSON.stringify(
|
|
||||||
process.env.ONE_MEMORY_COMMIT ?? "unknown",
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@one-memory/site",
|
"name": "@one-memory/site",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@one-memory/web",
|
"name": "@one-memory/web",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# MemHub 安装与发行
|
# MemHub 安装与发行
|
||||||
|
|
||||||
MemHub `v1.0.1`(build 5)的发行目标如下:
|
MemHub `v1.0.2`(build 6)的发行目标如下:
|
||||||
|
|
||||||
| 平台 | 发行物 | 构建与验收 |
|
| 平台 | 发行物 | 构建与验收 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
|||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "one-memory",
|
"name": "one-memory",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"release": {
|
"release": {
|
||||||
"build_number": 5,
|
"build_number": 6,
|
||||||
"macos_min_version": "13.0",
|
"macos_min_version": "13.0",
|
||||||
"app_update": "manual",
|
"app_update": "manual",
|
||||||
"runtime_manifest_schema": 1,
|
"runtime_manifest_schema": 1,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@one-memory/contracts",
|
"name": "@one-memory/contracts",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"types": "./src/index.ts",
|
"types": "./src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@one-memory/host-bridge",
|
"name": "@one-memory/host-bridge",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@one-memory/operations",
|
"name": "@one-memory/operations",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"types": "./src/index.ts",
|
"types": "./src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@one-memory/runtime-control",
|
"name": "@one-memory/runtime-control",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
@@ -217,6 +217,50 @@ describe("local Runtime installation", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
it("ignores Finder metadata without weakening payload verification", async () => {
|
||||||
|
const value = await distribution();
|
||||||
|
const plan = await planLocalInstall({
|
||||||
|
source: value.source,
|
||||||
|
installRoot: value.installRoot,
|
||||||
|
platform: process.platform,
|
||||||
|
arch: process.arch,
|
||||||
|
});
|
||||||
|
const first = await applyLocalInstall(plan);
|
||||||
|
await writeFile(path.join(plan.version_directory, ".DS_Store"), "finder");
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
verifyInstalledDistribution(
|
||||||
|
plan.version_directory,
|
||||||
|
process.platform,
|
||||||
|
process.arch,
|
||||||
|
),
|
||||||
|
).resolves.toMatchObject({ version: plan.version });
|
||||||
|
const repeated = await applyLocalInstall(plan);
|
||||||
|
expect(first.created_version).toBe(true);
|
||||||
|
expect(repeated.created_version).toBe(false);
|
||||||
|
|
||||||
|
await writeFile(
|
||||||
|
path.join(plan.version_directory, "unexpected-runtime-file"),
|
||||||
|
"not metadata",
|
||||||
|
);
|
||||||
|
await expect(
|
||||||
|
verifyInstalledDistribution(
|
||||||
|
plan.version_directory,
|
||||||
|
process.platform,
|
||||||
|
process.arch,
|
||||||
|
),
|
||||||
|
).rejects.toThrow("distribution_manifest_incomplete");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not let a non-file masquerade as Finder metadata", async () => {
|
||||||
|
const value = await distribution();
|
||||||
|
await symlink("../outside", path.join(value.source, ".DS_Store"));
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
verifyInstalledDistribution(value.source, process.platform, process.arch),
|
||||||
|
).rejects.toThrow("distribution_path_invalid");
|
||||||
|
});
|
||||||
|
|
||||||
it.each(["extra", "hash"])(
|
it.each(["extra", "hash"])(
|
||||||
"same-version reuse still rejects %s changes to installed canonical trees",
|
"same-version reuse still rejects %s changes to installed canonical trees",
|
||||||
async (change) => {
|
async (change) => {
|
||||||
|
|||||||
@@ -622,6 +622,12 @@ async function treeFiles(root: string, current = root): Promise<string[]> {
|
|||||||
entries.sort((left, right) => left.name.localeCompare(right.name));
|
entries.sort((left, right) => left.name.localeCompare(right.name));
|
||||||
const result: string[] = [];
|
const result: string[] = [];
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
|
// Finder may add this metadata file after installation. It is not Runtime
|
||||||
|
// payload and must not make an otherwise verified install unreusable.
|
||||||
|
if (entry.name === ".DS_Store") {
|
||||||
|
if (!entry.isFile()) throw new Error("distribution_path_invalid");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const target = path.join(current, entry.name);
|
const target = path.join(current, entry.name);
|
||||||
const relative = path.relative(root, target);
|
const relative = path.relative(root, target);
|
||||||
if (!safeRelative(relative)) throw new Error("distribution_path_invalid");
|
if (!safeRelative(relative)) throw new Error("distribution_path_invalid");
|
||||||
|
|||||||
@@ -256,6 +256,7 @@ describe("installPackagedRuntime", () => {
|
|||||||
});
|
});
|
||||||
const selectedBefore = await readlink(current);
|
const selectedBefore = await readlink(current);
|
||||||
const inodeBefore = (await lstat(current)).ino;
|
const inodeBefore = (await lstat(current)).ino;
|
||||||
|
await writeFile(path.join(current, ".DS_Store"), "finder metadata");
|
||||||
await expect(
|
await expect(
|
||||||
installPackagedRuntime({
|
installPackagedRuntime({
|
||||||
resourcesPath: resources,
|
resourcesPath: resources,
|
||||||
|
|||||||
@@ -429,6 +429,11 @@ async function treeFiles(root: string, current = root): Promise<string[]> {
|
|||||||
entries.sort((left, right) => left.name.localeCompare(right.name));
|
entries.sort((left, right) => left.name.localeCompare(right.name));
|
||||||
const result: string[] = [];
|
const result: string[] = [];
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
|
// Finder metadata is not part of the packaged Runtime identity.
|
||||||
|
if (entry.name === ".DS_Store") {
|
||||||
|
if (!entry.isFile()) throw new Error("distribution_path_invalid");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const target = path.join(current, entry.name);
|
const target = path.join(current, entry.name);
|
||||||
const relative = path.relative(root, target);
|
const relative = path.relative(root, target);
|
||||||
if (entry.isSymbolicLink()) {
|
if (entry.isSymbolicLink()) {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ test("installation docs disclose publication and lifecycle boundaries", async ()
|
|||||||
);
|
);
|
||||||
|
|
||||||
assert.match(readme, /公共 Python SDK 为 `hy-memory==2\.0\.2`/);
|
assert.match(readme, /公共 Python SDK 为 `hy-memory==2\.0\.2`/);
|
||||||
assert.match(readme, /`v1\.0\.1` 的发行目标是 Linux x64\/arm64 Runtime/);
|
assert.match(readme, /`v1\.0\.2` 的发行目标是 Linux x64\/arm64 Runtime/);
|
||||||
assert.match(readme, /Linux 制品由 GitHub Actions/);
|
assert.match(readme, /Linux 制品由 GitHub Actions/);
|
||||||
assert.match(readme, /macOS.*Developer ID 签名、公证/s);
|
assert.match(readme, /macOS.*Developer ID 签名、公证/s);
|
||||||
assert.match(readme, /release-manifest\.json/);
|
assert.match(readme, /release-manifest\.json/);
|
||||||
|
|||||||
Reference in new issue
Block a user