Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
bc432f6
Register DeepSeek V4.1 Flash image and thinking support.
Wang-Daoji Sep 15, 2026
907fdea
feat(desktop): add Mid-Autumn campaign prompt and token credit toast
Sep 17, 2026
880fef3
Merge pull request #467 from dolnna/feat/mid-autumn-campaign-prompt
dolnna Sep 17, 2026
9ffd443
fix(desktop): persist custom models and last-used model in account mode
xuzhiqin1990 Sep 20, 2026
201a6e5
feat: add lottery campaign rewards
Sep 20, 2026
2dfb397
Merge pull request #498 from antalike/drawing
dolnna Sep 20, 2026
100890c
Merge pull request #512 from MemTensor/main
syzsunshine219 Sep 21, 2026
e66a03b
Merge pull request #516 from shinetata/feature/personal-knowledge-base
syzsunshine219 Sep 22, 2026
2c01b07
Merge pull request #491 from xuzhiqin1990/fix/desktop-custom-model-pe…
syzsunshine219 Sep 22, 2026
a34c6bd
feat: show the campaign prompt only after login
Sep 22, 2026
edb0814
feat: delay the campaign prompt until setup finishes
Sep 22, 2026
47ce73b
feat: show the BYOK campaign prompt after guidance
Sep 22, 2026
69d7ff6
Merge pull request #518 from antalike/drawing
syzsunshine219 Sep 22, 2026
77b6f84
feat: show the campaign prompt after workspace entry
Sep 22, 2026
f7854b8
fix(desktop): ignore memory-service during Windows updates
Sep 22, 2026
9f51443
fix(desktop): keep Windows startup alive through splash timeout
Sep 22, 2026
72ac337
fix(desktop): stop Windows memory service on exit by default
Sep 22, 2026
90c24b9
fix(agent): persist WebUI transcript event timestamps
Sep 22, 2026
7a67d0d
Merge pull request #521 from antalike/drawing
syzsunshine219 Sep 22, 2026
99f1630
Merge pull request #520 from MemTensor/fix/memmy-504-windows-update
Memtensor-AI Sep 22, 2026
2b26a5c
Merge pull request #523 from MemTensor/fix/memmy-494-stop-windows-mem…
Memtensor-AI Sep 22, 2026
56234d7
Merge pull request #522 from MemTensor/fix/memmy-495-startup
Memtensor-AI Sep 22, 2026
6742aae
Merge pull request #524 from MemTensor/fix/memmy-434-webui-timestamps
Memtensor-AI Sep 22, 2026
206fad7
fix(desktop): hide standalone search ownership label
Sep 22, 2026
ef1418e
fix(chat): de-emphasize recovered tool errors
Sep 22, 2026
7d585f1
fix(upgrade): prefer packaged cloud endpoint over stale env
Sep 22, 2026
71ecbe7
Merge pull request #525 from syzsunshine219/codex/memmy-517-search-pa…
syzsunshine219 Sep 22, 2026
cad218a
Merge pull request #526 from syzsunshine219/codex/memmy-590-agent-loo…
syzsunshine219 Sep 22, 2026
cc98b69
fix(upgrade): ignore stale cloud URL in packaged runtimes
Sep 22, 2026
50ed14c
test(packaging): guard manifest cloud endpoint loading
Sep 22, 2026
0419451
Merge pull request #527 from syzsunshine219/codex/memmy-600-upgrade-c…
syzsunshine219 Sep 22, 2026
d9e2dd2
chore(release): bump Memmy to 1.1.8
syzsunshine219 Sep 22, 2026
219806f
chore(release): sync App agent version
syzsunshine219 Sep 22, 2026
0a7e11d
chore(release): sync desktop version
syzsunshine219 Sep 22, 2026
3264f2b
chore(release): sync backend version
syzsunshine219 Sep 22, 2026
1f430a7
chore(release): sync root lockfile to 1.1.8
syzsunshine219 Sep 22, 2026
f9a7b23
chore(release): sync agent lockfile to 1.1.8
syzsunshine219 Sep 22, 2026
5a545f4
docs(release): add Memmy v1.1.8 release notes
syzsunshine219 Sep 22, 2026
edb6677
chore(release): remove misplaced nested release notes
syzsunshine219 Sep 22, 2026
ad55d9d
docs(release): add Memmy v1.1.8 release notes
syzsunshine219 Sep 22, 2026
8d39dc6
Merge pull request #529 from MemTensor/release/v1.1.8
syzsunshine219 Sep 23, 2026
0841b43
Merge remote-tracking branch 'upstream/main' into feature/add-deepsee…
Wang-Daoji Sep 23, 2026
1c33f8f
Merge remote-tracking branch 'upstream/v1.1.9' into feature/add-deeps…
Wang-Daoji Sep 23, 2026
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
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Desktop edition (cn or intl; defaults to intl)
MEMMY_APP_EDITION=intl

# Cloud service
MEMMY_CLOUD_SERVICE=https://memmy-api.memtensor.cn
# Cloud service origin (clients append /api/... paths)
MEMMY_CLOUD_SERVICE=https://api.memmy.cn

# Analytics (GA4)
MEMMY_GA4_MEASUREMENT_ID=
Expand Down
13 changes: 13 additions & 0 deletions .github/release-notes/v1.1.8.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Memmy v1.1.8

## Highlights

- Fixed Windows startup lifecycle handling reported by MEMMY-495.
- Fixed Windows memory-service cleanup and update blocking reported by MEMMY-504 and MEMMY-494.
- Persisted monotonic WebUI transcript event timestamps reported by MEMMY-434.

## Upgrade notes

- Memmy Desktop and Agent are version 1.1.8.
- The bundled Memory service, viewer, and CLI remain independently versioned.
- The Memory protocol remains version 1.
32 changes: 31 additions & 1 deletion App/backend/local-api-contracts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,35 @@ export const PromotionFlagsSchema = z.object({
});
export type PromotionFlags = z.infer<typeof PromotionFlagsSchema>;

export const LotteryStatusSchema = z.object({
shouldShow: z.boolean(),
startAt: z.number().int().nonnegative(),
endAt: z.number().int().nonnegative(),
serverNow: z.number().int().nonnegative(),
landingUrl: z.string().url()
}).refine((status) => status.endAt > status.startAt, {
message: "Lottery endAt must be later than startAt",
path: ["endAt"]
});
export type LotteryStatus = z.infer<typeof LotteryStatusSchema>;

export const LotteryRewardSchema = z.discriminatedUnion("hasReward", [
z.object({
hasReward: z.literal(false)
}),
z.object({
hasReward: z.literal(true),
drawId: z.string().min(1).optional(),
tokenAmount: z.number().int().positive()
})
]);
export type LotteryReward = z.infer<typeof LotteryRewardSchema>;

export const LotteryRewardAckInputSchema = z.object({
drawId: z.string().min(1).optional()
});
export type LotteryRewardAckInput = z.infer<typeof LotteryRewardAckInputSchema>;

export const AppBootstrapResponseSchema = z.object({
app: AppSettingsDtoSchema,
onboarding: OnboardingStateDtoSchema,
Expand All @@ -611,7 +640,8 @@ export const AppBootstrapResponseSchema = z.object({
legal: LegalAgreementUrlsSchema.optional(),
// Src module.
// Promotions.
promotions: PromotionFlagsSchema.optional()
promotions: PromotionFlagsSchema.optional(),
lotteryStatus: LotteryStatusSchema.optional()
});
export type AppBootstrapResponse = z.infer<typeof AppBootstrapResponseSchema>;

Expand Down
21 changes: 21 additions & 0 deletions App/backend/src/adapters/inbound/local-api/routes/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
AccountProfileViewSchema,
AccountSessionViewSchema,
AvatarOptionSchema,
LotteryRewardAckInputSchema,
LotteryRewardSchema,
OkResponseSchema,
SendCodeInputSchema,
SendCodeResponseSchema,
Expand Down Expand Up @@ -122,6 +124,25 @@ export function registerAccountRoutes(app: FastifyInstance, options: RegisterAcc
})
);

app.get(
"/api/account/lottery/reward",
{ preHandler: options.authenticateRuntimeToken },
withErrorEnvelope(async (_request, reply) => {
const response = LotteryRewardSchema.parse(await options.account.getLotteryReward());
return reply.send(response);
})
);

app.post(
"/api/account/lottery/reward/ack",
{ preHandler: options.authenticateRuntimeToken },
withErrorEnvelope(async (request, reply) => {
const input = LotteryRewardAckInputSchema.parse(request.body);
const response = OkResponseSchema.parse(await options.account.ackLotteryReward(input));
return reply.send(response);
})
);

app.get(
"/api/account/avatars",
{ preHandler: options.authenticateRuntimeToken },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,34 @@ describe("account local api routes", () => {
]);
});

it("forwards lottery reward lookup and acknowledgement through AccountService", async () => {
const calls: unknown[] = [];
app = createServer({
account: {
async getLotteryReward() {
calls.push("get");
return { hasReward: true as const, drawId: "1", tokenAmount: 500_000 };
},
async ackLotteryReward(input: unknown) {
calls.push({ ack: input });
return { ok: true };
}
}
});

const reward = await app.inject({
method: "GET",
url: "/api/account/lottery/reward",
headers: { "x-memmy-local-token": "test-token" }
});
const ack = await injectJson("POST", "/api/account/lottery/reward/ack", { drawId: "1" });

expect(reward.statusCode).toBe(200);
expect(reward.json()).toEqual({ hasReward: true, drawId: "1", tokenAmount: 500_000 });
expect(ack.json()).toEqual({ ok: true });
expect(calls).toEqual(["get", { ack: { drawId: "1" } }]);
});

it("lists avatars and stores the selected avatar behind the runtime token", async () => {
const calls: string[] = [];
app = createServer({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
LegalAgreementUrlsSchema,
IntegrationToolResultSchema,
InvitationResultSchema,
LotteryRewardSchema,
LotteryStatusSchema,
OkResponseSchema,
PromotionFlagsSchema,
QWEN_ASR_MODEL_ID,
Expand All @@ -20,6 +22,8 @@ import {
type IntegrationConnection,
type IntegrationConnectionsResponse,
type LegalAgreementUrls,
type LotteryReward,
type LotteryStatus,
type IntegrationToolResult,
type OkResponse,
type PromotionFlags,
Expand All @@ -45,10 +49,12 @@ import type {
CloudStartSocialLoginResult,
CloudLogoutInput,
GetAccountInfoInput,
GetLotteryRewardInput,
EnsureInvitationCodeInput,
GetTokenQuotaEligibilityInput,
GetTokenUsageInput,
GrantTokensInput,
AckLotteryRewardInput,
ReleaseCheckResult,
RequestTokenQuotaInput,
TokenQuotaApplyResult,
Expand Down Expand Up @@ -414,6 +420,36 @@ export function createHttpCloudClient(options: CreateHttpCloudClientOptions = {}
} catch {
return undefined;
}
},

async getLotteryStatus(): Promise<LotteryStatus | undefined> {
try {
const data = await requestCloudData<unknown>(fetchImpl, baseUrl, timeoutMs, "/api/memmy/lottery/status", {
method: "GET",
lang: "zh"
});
const parsed = LotteryStatusSchema.safeParse(data);
return parsed.success ? parsed.data : undefined;
} catch {
return undefined;
}
},

async getLotteryReward(input: GetLotteryRewardInput): Promise<LotteryReward> {
const data = await requestCloudData<unknown>(fetchImpl, baseUrl, timeoutMs, "/api/memmy/lottery/reward", {
method: "GET",
lang: "zh",
bearerCredential: input.uuid
});
return LotteryRewardSchema.parse(data);
},

async ackLotteryReward(input: AckLotteryRewardInput): Promise<void> {
await requestBoolean(fetchImpl, baseUrl, timeoutMs, "/api/memmy/lottery/reward/ack", {
body: input.drawId ? { drawId: input.drawId } : {},
lang: "zh",
bearerCredential: input.uuid
});
}
};
}
Expand Down
2 changes: 2 additions & 0 deletions App/backend/src/adapters/outbound/cloud-client/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { createHttpCloudClient, type CreateHttpCloudClientOptions } from "./http-cloud-client.js";
export type {
AckLotteryRewardInput,
CheckReleaseInput,
CloudAsrTranscriptionInput,
CloudAsrTranscriptionResult,
Expand All @@ -18,6 +19,7 @@ export type {
CloudStartSocialLoginResult,
CloudLogoutInput,
GetAccountInfoInput,
GetLotteryRewardInput,
GetTokenQuotaEligibilityInput,
GetTokenUsageInput,
GrantTokensInput,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1101,6 +1101,112 @@ describe("cloud client", () => {
await expect(client.getPromotions()).resolves.toBeUndefined();
});

it("http client fetches the remote lottery status", async () => {
const requests: Array<{ path: string; method: string | undefined }> = [];
const lotteryStatus = {
shouldShow: true,
startAt: 1790121600000,
endAt: 1790812800000,
serverNow: 1790456789000,
landingUrl: "https://memmy.cn/activity/mid-autumn"
};
server = createServer((request, response) => {
requests.push({
path: request.url ?? "",
method: request.method
});
sendJson(response, { code: 0, message: "ok", data: lotteryStatus });
});
await listen(server);
const address = server.address();
if (!address || typeof address === "string") {
throw new Error("Mock cloud server did not bind to a port");
}
const client = createHttpCloudClient({ baseUrl: `http://127.0.0.1:${address.port}`, timeoutMs: 1000 });

await expect(client.getLotteryStatus()).resolves.toEqual(lotteryStatus);
expect(requests).toEqual([{
path: "/api/memmy/lottery/status",
method: "GET"
}]);
});

it("http client fails closed when the lottery status is invalid", async () => {
server = createServer((_request, response) => {
sendJson(response, {
code: 0,
message: "ok",
data: {
shouldShow: true,
startAt: 1790121600000,
endAt: 1790812800000
}
});
});
await listen(server);
const address = server.address();
if (!address || typeof address === "string") {
throw new Error("Mock cloud server did not bind to a port");
}
const client = createHttpCloudClient({ baseUrl: `http://127.0.0.1:${address.port}`, timeoutMs: 1000 });

await expect(client.getLotteryStatus()).resolves.toBeUndefined();
});

it("http client reads and acknowledges a lottery reward with account authorization", async () => {
const requests: Array<{
path: string;
method: string | undefined;
body: unknown;
authorization: string | undefined;
}> = [];
server = createServer(async (request, response) => {
requests.push({
path: request.url ?? "",
method: request.method,
body: await readJson(request),
authorization: request.headers.authorization
});
sendJson(response, request.url === "/api/memmy/lottery/reward"
? {
code: 0,
message: "ok",
data: { hasReward: true, drawId: "1", tokenAmount: 500_000 }
}
: { code: 0, message: "ok", data: true });
});
await listen(server);
const address = server.address();
if (!address || typeof address === "string") {
throw new Error("Mock cloud server did not bind to a port");
}
const client = createHttpCloudClient({ baseUrl: `http://127.0.0.1:${address.port}`, timeoutMs: 1000 });

await expect(client.getLotteryReward({ uuid: "cloud.login.uuid" })).resolves.toEqual({
hasReward: true,
drawId: "1",
tokenAmount: 500_000
});
await expect(client.ackLotteryReward({
uuid: "cloud.login.uuid",
drawId: "1"
})).resolves.toBeUndefined();
expect(requests).toEqual([
{
path: "/api/memmy/lottery/reward",
method: "GET",
body: {},
authorization: "Bearer cloud.login.uuid"
},
{
path: "/api/memmy/lottery/reward/ack",
method: "POST",
body: { drawId: "1" },
authorization: "Bearer cloud.login.uuid"
}
]);
});

});

function listen(serverToListen: ReturnType<typeof createServer>): Promise<void> {
Expand Down
17 changes: 17 additions & 0 deletions App/backend/src/adapters/outbound/cloud-client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import type {
LegalAgreementUrls,
IntegrationToolResult,
InvitationResult,
LotteryReward,
LotteryStatus,
OkResponse,
PromotionFlags,
SocialLoginProvider,
Expand Down Expand Up @@ -129,6 +131,15 @@ export interface GrantTokensInput {
uuid?: string;
}

export interface GetLotteryRewardInput {
uuid: string;
}

export interface AckLotteryRewardInput {
uuid: string;
drawId?: string;
}

/** Contract for request token quota input. */
export interface RequestTokenQuotaInput {
/** Uuid. */
Expand Down Expand Up @@ -254,4 +265,10 @@ export interface CloudClient {
getLegalUrls(): Promise<LegalAgreementUrls | undefined>;
/** Reads get promotions. */
getPromotions(): Promise<PromotionFlags | undefined>;
/** Reads the remotely controlled lottery campaign status. */
getLotteryStatus(): Promise<LotteryStatus | undefined>;
/** Reads the latest unacknowledged lottery reward for the account. */
getLotteryReward(input: GetLotteryRewardInput): Promise<LotteryReward>;
/** Marks the latest matching lottery reward as shown. */
ackLotteryReward(input: AckLotteryRewardInput): Promise<void>;
}
Loading
Loading