Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
894aa9e
feat(goal): add headless CLI execution
ZongYue99 Aug 19, 2026
dc6230c
fix(goal): preserve multiline objectives
ZongYue99 Aug 19, 2026
b6a6215
feat(memory): support configurable retrieval layers
ZongYue99 Aug 19, 2026
fe9556c
fix(goal): enforce headless project binding
ZongYue99 Aug 19, 2026
857f41e
chore: merge upstream v1.1.1 into goal headless memory eval
ZongYue99 Aug 25, 2026
d79a24b
goal headless memory eval
ZongYue99 Aug 25, 2026
1ce7e59
feat(linux): add one-line CLI installer
Aug 25, 2026
df97e71
feat(linux): add one-line CLI installer
ZongYue99 Aug 25, 2026
16af3ba
fix(desktop): preserve Unicode Windows update paths
Aug 25, 2026
6cf1a0a
Merge pull request #298 from EF1874/codex/fix-v1.1.1-installer-issue
wustzdy Aug 25, 2026
9dc056b
fix(models): restore local embedding selection
EF1874 Aug 25, 2026
2581685
fix(frontend): keep Windows memory refresh clickable
Aug 25, 2026
92dd9d7
fix(frontend): avoid Windows memory drawer controls
Aug 25, 2026
6c3e5a9
fix(frontend): prune stale model assignments on delete
Aug 25, 2026
d9c90b3
fix(models): restore local embedding selection
ZongYue99 Aug 25, 2026
80ed088
fix(models): sync local selections across account mode
EF1874 Aug 26, 2026
554ae14
Merge pull request #304 from Memtensor-AI/codex/memmy-316-v1.1.1
syzsunshine219 Aug 26, 2026
bb7974e
Merge pull request #303 from Memtensor-AI/codex/memmy-317-v1.1.1
syzsunshine219 Aug 26, 2026
41310ac
Merge v1.1.1 into codex/memmy-318-v1.1.1
Aug 26, 2026
7121c0e
Merge pull request #302 from Memtensor-AI/codex/memmy-318-v1.1.1
syzsunshine219 Aug 26, 2026
1226efe
Merge pull request #305 from EF1874/codex/restore-local-embedding-v1.1.1
syzsunshine219 Aug 26, 2026
19641f0
chore(release): prepare Memmy v1.1.1
Aug 26, 2026
e519dce
fix(linux): install agent dependencies before CLI build
Aug 26, 2026
65060a1
test(memory): isolate OpenCode setup environment
Aug 26, 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
25 changes: 25 additions & 0 deletions .github/release-notes/v1.1.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Memmy v1.1.1

## Highlights

- Added headless Goal execution for Memmy Agent, including durable project binding and multiline objectives, so long-running automation can be launched and resumed from the CLI without an interactive terminal.
- Added a one-line Linux CLI installer with managed systemd gateway lifecycle commands and documented setup paths for both international and China users.
- Restored local embedding model selection and made account-mode model changes stay synchronized across backend state, runtime configuration, and Desktop settings.

## Agent and CLI improvements

- Goal commands now preserve multiline instructions, resolve the intended terminal target, and require the correct project binding before headless execution.
- Linux installs can build and verify the CLI archive, bootstrap Memmy with a single command, and manage the local gateway through systemd with explicit start, stop, restart, status, and log flows.
- Root-terminal option handling and gateway lifecycle coverage were expanded to keep interactive and headless entry points consistent.

## Memory improvements

- Memory retrieval layers can now be configured explicitly, including bounded observation and session-layer behavior used by Agent integrations.
- Memory CLI setup and generated Agent instructions now carry the required project and retrieval configuration without losing existing user settings.
- Session turn capture, REST contracts, and Memory configuration tests were extended for the new retrieval behavior.

## Desktop and setup reliability

- Local embedding selections are preserved when switching account modes, while deleted models are pruned from stale workspace assignments and runtime projections.
- Windows keeps memory refresh available without exposing unsupported drawer controls, and update launching now preserves Unicode installer paths.
- BYOK setup and model workspace feedback more clearly reflect saved, synchronized selections across login, settings, and token-detail flows.
209 changes: 209 additions & 0 deletions .github/workflows/linux-cli-installer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
name: Linux CLI installer

on:
release:
types: [published]
pull_request:
paths:
- ".github/workflows/linux-cli-installer.yml"
- "scripts/install.sh"
- "scripts/internal/linux/**"
- "App/memmy-agent/**"
- "Memory/**"
- "Migrations/**"
- "App/backend/**"
- "tests/linux-cli-packaging.test.mjs"
- "package.json"
- "package-lock.json"
push:
branches: [main]
paths:
- ".github/workflows/linux-cli-installer.yml"
- "scripts/install.sh"
- "scripts/internal/linux/**"
- "App/memmy-agent/**"
- "Memory/**"
- "Migrations/**"
- "App/backend/**"
- "tests/linux-cli-packaging.test.mjs"
- "package.json"
- "package-lock.json"
workflow_dispatch:
inputs:
version:
description: Version matching package.json (without the v prefix)
required: false
type: string
upload_to_release:
description: Upload the verified Linux assets to an existing GitHub Release
required: false
default: false
type: boolean

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-24.04
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event_name == 'release' && github.event.release.tag_name || github.sha }}
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm ci --prefix App/memmy-agent
- name: Resolve and verify version
id: version
env:
REQUESTED_VERSION: ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.version }}
run: |
set -euo pipefail
repository_version="$(node -p "require('./package.json').version")"
version="${REQUESTED_VERSION:-$repository_version}"
version="${version#v}"
if [[ "$version" != "$repository_version" ]]; then
echo "Requested version $version does not match package.json version $repository_version" >&2
exit 1
fi
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Test Linux launcher and installer contracts
run: npm run test:linux-cli
- name: Build Linux CLI release assets
env:
VERSION: ${{ steps.version.outputs.version }}
run: bash scripts/internal/linux/build-cli-archive.sh --version "$VERSION" --output release-assets
- uses: actions/upload-artifact@v4
with:
name: memmy-linux-cli-assets
path: |
release-assets/memmy-agent-linux-cli.tar.gz
release-assets/memmy-agent-linux-cli.tar.gz.sha256
release-assets/install.sh
if-no-files-found: error

install-smoke:
needs: build
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
architecture: x64
- runner: ubuntu-24.04-arm
architecture: arm64
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/download-artifact@v4
with:
name: memmy-linux-cli-assets
path: release-assets
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Install twice and exercise the CLI
env:
VERSION: ${{ needs.build.outputs.version }}
EXPECTED_ARCH: ${{ matrix.architecture }}
MEMMY_INSTALL_ROOT: ${{ runner.temp }}/memmy-agent
MEMMY_BIN_DIR: ${{ runner.temp }}/bin
run: |
set -euo pipefail
case "$(uname -m)" in
x86_64|amd64) actual_arch="x64" ;;
aarch64|arm64) actual_arch="arm64" ;;
*) echo "Unsupported runner architecture: $(uname -m)" >&2; exit 1 ;;
esac
[[ "$actual_arch" == "$EXPECTED_ARCH" ]]
export MEMMY_VERSION="$VERSION"
export MEMMY_RELEASE_BASE_URL="file://$PWD/release-assets"
bash release-assets/install.sh
bash release-assets/install.sh
"$MEMMY_BIN_DIR/memmy" --version
"$MEMMY_BIN_DIR/memmy" --help >/dev/null
"$MEMMY_BIN_DIR/memmy-memory" health >/dev/null
systemctl --user is-enabled --quiet memmy-memory.service
if systemctl --user is-enabled --quiet memmy-gateway.service; then
echo "Gateway must remain disabled until the first bare memmy onboarding" >&2
exit 1
fi
systemd_verify_output="$(systemd-analyze --user verify \
"$HOME/.config/systemd/user/memmy-memory.service" \
"$HOME/.config/systemd/user/memmy-gateway.service" 2>&1)" || {
printf '%s\n' "$systemd_verify_output" >&2
exit 1
}
if [[ -n "$systemd_verify_output" ]]; then
printf '%s\n' "$systemd_verify_output" >&2
exit 1
fi
export MEMMY_SMOKE_SECRET="linux-smoke-${EXPECTED_ARCH}"
(
cd "$MEMMY_INSTALL_ROOT/current/App/memmy-agent"
CONFIG_PATH="$HOME/.memmy/config.yaml" node --input-type=module <<'NODE'
import { readFileSync, writeFileSync } from "node:fs";
import YAML from "yaml";

const configPath = process.env.CONFIG_PATH;
const config = YAML.parse(readFileSync(configPath, "utf8")) ?? {};
config.agents ??= {};
config.agents.defaults ??= {};
config.agents.defaults.model = "ollama/llama3.2";
config.tools ??= {};
config.tools.web ??= {};
config.tools.web.search ??= {};
config.tools.web.search.apiKey = "${MEMMY_SMOKE_SECRET}";
config.tools.browser = { enabled: false };
writeFileSync(configPath, YAML.stringify(config), "utf8");
NODE
)
set +e
timeout --signal=TERM --kill-after=5s 45s \
script -qefc "$MEMMY_BIN_DIR/memmy" "$RUNNER_TEMP/memmy-tui.log" </dev/null
tui_status=$?
set -e
if [[ "$tui_status" -ne 0 && "$tui_status" -ne 124 && "$tui_status" -ne 137 ]]; then
cat "$RUNNER_TEMP/memmy-tui.log" >&2
exit "$tui_status"
fi
systemctl --user is-enabled --quiet memmy-gateway.service
systemctl --user is-active --quiet memmy-gateway.service
grep -Fq 'MEMMY_SMOKE_SECRET="linux-smoke-' "$HOME/.memmy/systemd/gateway.env"
gateway_pid_before_update="$(systemctl --user show memmy-gateway.service --property=MainPID --value)"
bash release-assets/install.sh
gateway_pid_after_update="$(systemctl --user show memmy-gateway.service --property=MainPID --value)"
[[ "$gateway_pid_after_update" != "$gateway_pid_before_update" ]]
systemctl --user is-active --quiet memmy-gateway.service
curl --fail --silent --show-error http://127.0.0.1:18970/health >/dev/null
"$MEMMY_BIN_DIR/memmy-memory" health >/dev/null
systemctl --user stop memmy-gateway.service

upload:
if: ${{ github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.upload_to_release == true) }}
needs: [build, install-smoke]
runs-on: ubuntu-24.04
permissions:
contents: write
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ needs.build.outputs.version }}
steps:
- uses: actions/download-artifact@v4
with:
name: memmy-linux-cli-assets
path: release-assets
- name: Upload only Linux CLI assets to the existing Release
run: |
set -euo pipefail
gh release view "v$VERSION" --repo "$GITHUB_REPOSITORY" >/dev/null
gh release upload "v$VERSION" \
release-assets/memmy-agent-linux-cli.tar.gz \
release-assets/memmy-agent-linux-cli.tar.gz.sha256 \
release-assets/install.sh \
--clobber \
--repo "$GITHUB_REPOSITORY"
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export interface AccountSessionRepository {
activateByCloudUuid(cloudUuid: string, accountChannel?: AccountChannel): boolean;
upsert(input: UpsertAccountSessionInput): AccountSessionView;
clear(): void;
/** Clears only when the expected cloud credential still belongs to the active session. */
clearIfCloudUuid(cloudUuid: string): boolean;
getLastCodeSentAt(key: string): string | null;
markCodeSent(key: string, at: string): void;
}
Expand Down Expand Up @@ -216,6 +218,12 @@ export function createAccountSessionRepository(db: DatabaseSync, secretStore: Se
setActiveAccountUuid(db, null);
},

clearIfCloudUuid(cloudUuid) {
if (getCloudUuidFromRow(secretStore, getActiveAccountRow(db)) !== cloudUuid) return false;
setActiveAccountUuid(db, null);
return true;
},

getLastCodeSentAt(key) {
const throttleKey = toThrottleKey(key);
const row = db
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,33 @@ describe("account session repository", () => {
expect(fabricatedAccount).toBeUndefined();
});

it("clears only the session whose cloud uuid is still active", () => {
tempDir = mkdtempSync(join(tmpdir(), "memmy-account-session-"));
const store = createAppStateStore({ databasePath: join(tempDir, "app.sqlite") });
store.repositories.accountSession.upsert({
profile: {
userId: "user-1",
email: "hello@example.com",
phoneNumber: null,
nickname: "hello",
avatarUrl: null,
planType: "free",
hasFinishedGuide: false,
region: null,
registeredAt: null,
rawProfile: { id: "user-1" }
},
uuid: "cloud-account-a",
cloudUuid: "cloud.login.uuid.a"
});

expect(store.repositories.accountSession.clearIfCloudUuid("cloud.login.uuid.b")).toBe(false);
expect(store.repositories.accountSession.get()).toMatchObject({ authenticated: true });
expect(store.repositories.accountSession.clearIfCloudUuid("cloud.login.uuid.a")).toBe(true);
expect(store.repositories.accountSession.get()).toEqual({ authenticated: false });
store.close();
});

it("infers a legacy login channel only from one unambiguous bound contact", () => {
tempDir = mkdtempSync(join(tmpdir(), "memmy-account-session-"));
const store = createAppStateStore({ databasePath: join(tempDir, "app.sqlite") });
Expand Down
Loading
Loading