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
35 changes: 17 additions & 18 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@ Memory, and Voice services.
- `make -C apps/teamhub test`: run TeamHub unit tests.
- `pnpm contracts:generate && pnpm contracts:verify`: regenerate and verify OpenAPI consumers.
- `make check BASE=<target-ref>`: run every app's fast check in parallel.
Fast checks are local and need no Postgres, containers, or full suite.
`BASE` is required because the built-in Skill immutability check compares
against it; the repository assumes no default branch.
- `make check-repository-gate BASE=<target-ref>`: run the repository gate
(migration history, commitlint, env inventory, changed-file format and lint,
script tests, doc links).
- Before opening a pull request, run the cheap gates listed under Important
Conventions. Those are not the full suite.

## Structure

Expand Down Expand Up @@ -58,29 +57,26 @@ Memory, and Voice services.
- Implement the simplest solution that satisfies the current request. Do not
design for requirements outside the current scope, and prefer existing
dependencies over new custom infrastructure.
- Before changing shared configuration (root or
shared tool configs, shared scripts, env defaults), find every consumer with
`rg` and check each affected app in the same pull request, not only the one being
fixed.
- After the feature works, perform one explicit deletion pass: identify and
remove code that is no longer needed and abstractions that the current
request does not require.
- A change to shared configuration (root or shared tool configs, shared
scripts, env defaults) is done when every app that consumes it passes its
fast checks in the same pull request, not only the one being fixed.
- A feature is done when no code it made unnecessary and no abstraction the
request does not require is left behind.
- When retiring or reshaping a feature, delete its code and drop its tables,
columns, rows, and stored files in a forward migration. Data that must
survive is converted once inside the migration; do not keep dual-read or
fallback paths for old shapes. This covers persisted state, config, and
caches written by the Daemon or CLI on users' machines, but not
compatibility with third-party tool versions such as Codex CLI.
- During local development, run the focused tests for the changed behavior and
the affected fast boundary checks. Focused Vitest or Go unit tests do not
replace those gates. Do not run complete integration, E2E, container, or
the affected fast checks. Focused Vitest or Go unit tests do not
replace those checks. Do not run complete integration, E2E, container, or
reliability suites by default; `make check BASE=<target-ref>` runs only the
fast checks. Run a focused heavy scenario when
changing that test or harness, reproducing its failure, or when real
database, process, concurrency, or recovery semantics cannot be proved at a
lower layer.
- Before opening a pull request, run the cheap gates for every touched app.
These are local and do not need Postgres, containers, or the full suite:
- Before opening a pull request, run the fast checks for every touched app:
- Web: `make check-web` (typecheck, unit tests, and React Doctor). If the
change adds a static import on the Room or app initial graph, also run
`pnpm --filter @linger/web run build:assets` to check the initial JS
Expand Down Expand Up @@ -120,8 +116,12 @@ Memory, and Voice services.
or would break a mode (for example `NODE_ENV`, `COMPOSE_PROJECT_NAME`).
Every fixed local default for `pnpm dev` lives in
`scripts/local-defaults.env`; `scripts/with-local-env.mjs` only merges files.
- The repository is English-only, human-facing docs included. Write new and
changed doc text in English and do not add Chinese to any existing doc.
- The repository is English-only: docs, code, comments, logs, user-facing
strings, prompts, Skills, tests, fixtures, and benchmark data.
`make check-repository-gate` rejects CJK ideographs in changed files.
- NO: a test fixture with a literal Chinese sentence.
- OK: the same text as escaped code points (`"\u4f60\u597d"`), or kana,
accented Latin, or emoji when any unspaced script will do.

## Cross-Module Conventions

Expand All @@ -147,7 +147,7 @@ to comply.
`HTTP`).
- TypeScript: export functions as `function` declarations; object types are
`interface` (lint-enforced); private class state uses `#field`.
- Comments are English and explain why or state an invariant; never restate
- Comments explain why or state an invariant; never restate
the code. No issue or pull request numbers, plan-step labels, or history
("previously", "used to"). Exported Go declarations and packages have doc
comments; `make -C apps/teamhub lint-go BASE=<target-ref>` checks this on new
Expand All @@ -156,8 +156,7 @@ to comply.
third-party error prose, model prose, or our own human-readable labels.
Carry the decision in structured fields, error codes, or model structured
output, and keep human-readable text for display only.
- Code, comments, logs, user-facing strings, prompts, Skills, tests, fixtures,
and benchmark data contain no Chinese. Serve users of any language through
- Serve users of any language through
language-agnostic mechanisms: engine language settings, structured fields,
model structured output, and prompts that answer in the user's language.
Never add per-language keyword lists, regexes, or model-facing examples.
Expand Down
172 changes: 6 additions & 166 deletions apps/desktop/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ Gateway and runs Agents. The daemon exits with the app.
engines pass them to the services whenever they are configured (an edit
runs a new job even after a finished one); Native also starts its bundled
LiveKit whenever this computer has voice tools, with or without a voice
key (see Release). There is no settings page
key (see Source Package and Release). There is no settings page
after setup yet: keys change on the next Install or Try Again.
- `src/main/diagnostics.ts`: Diagnostics, "Show Logs" (opens
`<userData>/logs`) and "Save Diagnostics" (a redacted
Expand Down Expand Up @@ -244,172 +244,12 @@ Gateway and runs Agents. The daemon exits with the app.
the app's own Node (`ELECTRON_RUN_AS_NODE`) in packaged builds and on the
`node` in PATH from source.

## Source Package
## Source Package and Release

Release builds set `LINGER_SOURCE_ARCHIVE_URL` (https) and
`LINGER_SOURCE_ARCHIVE_SHA256` before `node build.mjs`. Produce them from the
release tag in the repository root:

```sh
SOURCE_ARCHIVE_REF=v1.2.3 \
SOURCE_ARCHIVE_URL_BASE=https://github.com/simplexai-labs/linger/releases/download/v1.2.3 \
make source-archive
```

Upload `.linger-runtime/source-archive/linger-v1.2.3.tar.gz` to that release
and take both values from `source-archive.json` next to it. One commit always
gives the same digest. To ship the package inside the app instead (about
45 MB more, no hosting needed), set `LINGER_SOURCE_ARCHIVE_FILE` to the
archive; the build copies it beside the installer and pins its digest. Without
either (and without `LINGER_CHECKOUT`) the build does not offer a
local install.

A downloaded source package can have a dependency store for each platform
beside it, so the native install of its packages needs no registry. Build it
from the same tag, on the platform it serves, and set
`LINGER_SOURCE_DEPENDENCIES_URL` and `LINGER_SOURCE_DEPENDENCIES_SHA256` from
`source-dependencies-<target>.json` for that platform's build:

```sh
SOURCE_ARCHIVE_REF=v1.2.3 \
SOURCE_ARCHIVE_URL_BASE=https://github.com/simplexai-labs/linger/releases/download/v1.2.3 \
make source-dependencies
```

`SOURCE_DEPENDENCIES_TARGET` builds another platform's store instead (the
`source-dependencies` CI job does this on Linux). Such a store skips install
scripts, so the install still compiles or downloads native modules such as
better-sqlite3. The desktop downloads the store after the source package
(resuming an interrupted download) and unpacks it into
`.linger-runtime/pnpm-store` of the source; when that fails it logs
`local_server.dependencies_failed` and the installer downloads the packages
itself.

`LINGER_SOURCE_ARCHIVE_MIRRORS` and `LINGER_SOURCE_DEPENDENCIES_MIRRORS` list
other https hosts serving the same files, separated by spaces. The desktop moves
to the next one when a host fails or stalls, and the SHA-256 still decides.

Native installs also need the publisher's pinned localmodels tool and native
PostgreSQL package for the target. They are pinned in
`distribution/<target>.json`, in the shape `packages/local-installation/src/native-dependencies.ts` reads
(`platform`, `arch`, `localmodelsTool`, `nativeSources`, `voiceTools`, each
source with an optional `mirrors` list of https URLs for the same file). The build copies the
file of its target (`LINGER_DESKTOP_DAEMON_TARGETS`, else this machine) beside
the bundled installer, which reads it at start; `LINGER_DESKTOP_DISTRIBUTION`
names another file instead. A source with `file` (Windows) instead of `url`
names an archive the app ships: the build copies it from
`LINGER_NATIVE_POSTGRES_DIR` (default `~/.cache/linger-desktop`) to
`dist/installer/native/` after checking its size and sha256, and the installer
checks them again before extracting it, as it does for a download. Where the
file has no PostgreSQL for the computer, or the bundled archive is missing,
the setup page does not offer installing on this computer. `src/main/distribution.test.ts` checks that each file holds its
target's PostgreSQL and the localmodels release `scripts/installs/stack.sh`
pins.

## Release

Each desktop target (`darwin-arm64`, `linux-x64`) gets its own distribution
file and build. For one release:

1. Make the source package as above and upload it. On a host of each target,
make its dependency store and upload it beside the package.
2. On a host of each target, build the PostgreSQL package with
`node scripts/prepare-native-postgres.mjs --output DIR --base-url URL`; it
refuses to cross-build. Before uploading, check `initdb`, a connection and
`CREATE EXTENSION vector` against a private data directory. Upload the
`.tar.gz` to `URL` and keep `postgres-<target>.json` from `DIR`. Windows
repackages the pinned EDB binaries, builds only pgvector and adds the
Visual C++ runtime the EDB zip leaves out, so run it there from an x64
Native Tools Command Prompt for Visual Studio. The installer accepts only
credential-free HTTPS URLs, so `URL` must be the HTTPS CDN.
3. For a new PostgreSQL or localmodels release, update
`distribution/<target>.json`: `localmodelsTool` for the localmodels
release `scripts/installs/stack.sh` pins, and `nativeSources` holding that target's
`postgres-<target>.json` object.
4. Build with `LINGER_DESKTOP_DAEMON_TARGETS` set to the target and the source package
(`LINGER_SOURCE_ARCHIVE_FILE`, or the URL and SHA256, plus the target's
`LINGER_SOURCE_DEPENDENCIES_URL` and SHA256) set, then package (`packaging/mac/package.py
--arch arm64` for macOS, `packaging/linux/package.py` for Linux).
5. Check the package on a clean computer: Quick Install with the default
source ends on "Linger is ready" and Open Linger shows the local server,
signed in, with this computer connected.

Official releases come from the public simplexai-labs/linger repository: a
`desktop-v<version>` tag pushed there runs steps 4 and the packaging for
`darwin-arm64`, `linux-x64` and `win32-x64` in GitHub Actions and publishes a
linger GitHub Release with `SHA256SUMS.txt`; see
[`docs/desktop-release.md`](../../docs/desktop-release.md). The workflow
fetches the bundled archives from the assets of the `native-deps` prerelease
there, so upload a new pinned archive to it before tagging.

Windows, Linux x64 and Apple silicon bundle PostgreSQL in the app instead
(`file` in `distribution/<target>.json`; 35 MB on Windows, 10–12 MB
elsewhere), so Native works without the CDN. Put the pinned archives in
`LINGER_NATIVE_POSTGRES_DIR` before `node build.mjs`; the build fails without
them. To download it instead, replace `file` with the HTTPS `url` once it is on
the CDN. Native PostgreSQL refuses an elevated Linger ("Run as
administrator"), as it refuses root elsewhere.

Every target also bundles the voice tools (`voiceTools`, about 40 MB each):
the official LiveKit server release (Apple silicon: built from the tagged
source with the pinned Go, since LiveKit publishes no macOS binary; without
cgo, so its vendored go-osstat reports an idle CPU, which only matters when
LiveKit spreads rooms over several servers) and a
static LGPL ffmpeg/ffprobe (Linux and Windows: built from the FFmpeg release
in the pinned Alpine image, Windows with MinGW-w64; Apple silicon: the
pinned serversideup LGPL build, which links OpenSSL statically). Make them on
Linux with `node scripts/prepare-native-voice-tools.mjs --target <target>
--output DIR` (Docker; Go for Apple silicon), copy the archive to
`LINGER_NATIVE_POSTGRES_DIR` and `voice-tools-<target>.json` from `DIR` into
`voiceTools`. The build is reproducible except for the Go toolchain download,
and it checks the versions, the ffmpeg components the voice workers use,
static linking and the Mach-O signatures. Licenses ship in `licenses/` of the
archive. On every install and start, whether or not a voice key is saved,
Native extracts the archive into
`tools/native/components`, puts its `bin` first on the services' `PATH`
(TeamHub and the voice workers run `ffmpeg`/`ffprobe` from it) and runs
`livekit-server` as the `livekit` Native service: loopback only, HTTP and
WebSocket on one free port and media on one free UDP port, both kept with
the generated API key and secret in the owner-only `livekit.env` of the
instance, logs in `native/logs/livekit.log`. ICE over TCP stays off because
LiveKit binds it on every interface. Without the tools for the computer, or
when they fail to prepare, Native logs it, keeps voice off, gives TeamHub
`TEAMHUB_VOICE_MEDIA=off` and finishes the install. The macOS tools are ad-hoc signed by their linker and
live in the archive, outside the app's signature, like PostgreSQL.

A build with a source package for `win32-x64`, `linux-x64` or `darwin-arm64`
also ships TeamHub prebuilt (`scripts/build-teamhub.mjs`): build.mjs
cross-compiles `teamhub`, `migrate` and `account` from that package (`go` in
PATH, `CGO_ENABLED=0`) into `dist/installer/native/teamhub-<target>/` with a
`manifest.json` of their sha256, the package's sha256 and the commit
`git archive` recorded in it; a downloaded package
(`LINGER_SOURCE_ARCHIVE_URL`) is fetched for this. The package scripts
refuse binaries that do not match the manifest or the bundled package
(`packaging/native_bundle.py`). The desktop copies them into the unpacked source
(`.simplexai-runtime/prebuilt/`), the installer checks them again and then
skips the Go download, and `scripts/source-native.mjs` runs them (checking the
sha256 before each start) instead of `go run` / `go build`. Without them, or
when they do not verify, Native downloads Go and compiles as before.

The same targets ship the services Native runs, built from that package
(`scripts/build-native-runtime.mjs`): Web's production files, Node
Services bundled with esbuild beside the node_modules it loads at runtime,
and the pinned Node.js, in `dist/installer/native/runtime-<target>/` with a
`manifest.json` (schema 1). Windows and Linux ship `runtime.tar.zst` beside
the Node.js release archive. Apple silicon ships the `runtime/` folder and
`node/bin/node` unpacked instead, because the app's signature must cover
every Mach-O file: `packaging/mac/package.py` signs them with the app (Node.js
with `node-entitlements.plist`: JIT, and native modules under the hardened
runtime its release signature keeps), records the signed bytes in the
manifests and seals them with a shallow signature. The desktop unpacks or
copies the services into `.simplexai-runtime/offline/` of the source and
Node.js into its own folder, removing `com.apple.quarantine` on macOS;
`checkNativeRuntime` then verifies every file, and Native installs no
packages and needs no registry. On Linux the services need glibc 2.35
(Ubuntu 22.04, Debian 12), like the PostgreSQL build; with an older glibc the
desktop leaves them out and Native installs from the network. Model data
(OCR, Voice) is not bundled: Node Services downloads it on first use.
Intel Macs have none of this and install from the network.
Read [Packaging Details](../../docs/desktop-release.md#packaging-details) before
changing `build.mjs`, `packaging/`, `distribution/`, the source package, or the
bundled native PostgreSQL, voice tools, TeamHub, or services, and before
building a package.

## Development Environment

Expand Down
7 changes: 6 additions & 1 deletion apps/voice-runtime/src/live-worker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,12 @@ describe("afterCall", () => {
expect(
afterCall("Switching everything to English.", "All English now"),
).toBe(" All English now");
expect(afterCall("改好了。", "主题是周五会议")).toBe("主题是周五会议");
expect(
afterCall(
"\u6539\u597d\u4e86\u3002",
"\u4e3b\u9898\u662f\u5468\u4e94\u4f1a\u8bae",
),
).toBe("\u4e3b\u9898\u662f\u5468\u4e94\u4f1a\u8bae");
expect(afterCall("", "Done.")).toBe("Done.");
expect(afterCall("Done. ", "Next")).toBe("Next");
});
Expand Down
Loading
Loading