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
2 changes: 1 addition & 1 deletion apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"pagefind": "^1.4.0",
"postcss": "^8.5.18",
"tailwindcss": "^4.1.18",
"typescript": "^5.9.3",
"typescript": "^6.0.3",
"vite": "^6.0.0",
"vitest": "^4.1.11"
}
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
"@/*": ["./src/*"]
},
"target": "ES2020",
"useDefineForClassFields": true,
Expand All @@ -16,6 +15,7 @@
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"types": ["node"],
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
Expand Down
8 changes: 8 additions & 0 deletions docs/architecture/desktop-auto-update.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,20 @@ The `dist:*` scripts pass `--publish never`: electron-builder generates the upda

The `.dmg` / `.exe` are the human-facing first installs; the `.zip` (Squirrel.Mac) and `latest*.yml` feeds are what the running app consumes. When a build is relocated from the build-source repo to the public distribution repo, these metadata files must move with it.

## TypeScript build toolchain

All workspaces that declare TypeScript, including the Agentlet and Agenetes subtrees and the documentation site, use the same `^6.0.3` dependency range and resolve to TypeScript `6.0.3` through the root `pnpm-lock.yaml`. Release builds use the workspace compiler selected by pnpm; no global compiler override is required.

The Agenetes instance, Agent Profile registry, and documentation site's mixed browser/SSR project explicitly load Node.js types with `types: ["node"]`, because TypeScript 6 no longer automatically includes visible `@types` packages. The documentation site resolves its `@/*` alias through `./src/*` without the deprecated `baseUrl` option.

## macOS signing dependency

Desktop packaging pins `electron-builder` to `26.16.1` in `apps/desktop/package.json` and resolves it through the shared `pnpm-lock.yaml`. This is the first stable v26 release containing the upstream [keychain password fix](https://github.com/electron-userland/electron-builder/pull/10172): `security import -P` needs the certificate password, but `security set-key-partition-list -k` needs the temporary keychain's own unlock password. Version `26.15.3` incorrectly reused the certificate password for the latter command, causing `SecKeychainUnlock` failures before signing or notarization.

Local builds, GitHub Actions, and Azure Pipelines use the same packaging dependency. Both CI paths install with `--frozen-lockfile`; no workflow-specific password substitution or signing bypass is required. Developer ID signing and Apple notarization remain enabled.

The workspace overrides the `node-gyp > undici` packaging dependency to the published `6.29.0` release, within node-gyp's declared `^6.25.0` range. The previous `6.29.1` lockfile resolution is unavailable from the configured package feed and prevents a frozen dependency restore. This scoped override does not change the application's runtime Undici dependency.

## Azure Pipelines packaging

The root [azure-pipelines.yml](../../azure-pipelines.yml) adds independent Microsoft-hosted Windows and macOS packaging jobs alongside the existing GitHub Actions release workflow. In Azure DevOps, select **New Pipeline > GitHub > microsoft/Huabu > Existing Azure Pipelines YAML file** and choose `/azure-pipelines.yml`; authorize repository access through the Azure DevOps GitHub integration. This pipeline publishes build artifacts only, not GitHub Releases, and does not change the application's GitHub update feed.
Expand Down
2 changes: 1 addition & 1 deletion external/agenetes/packages/acp-driver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"zod": "^4.3.6"
},
"devDependencies": {
"typescript": "^5.9.0",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
}
}
2 changes: 1 addition & 1 deletion external/agenetes/packages/agenetes/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
},
"devDependencies": {
"@types/node": "^22.15.0",
"typescript": "^5.9.0",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
// minus its tests (tsconfig `exclude`), and a test-only harness has no
// business in the shipped `dist`.

import { normalize } from 'node:path';
import { fileURLToPath } from 'node:url';

import { describe, expect, it } from 'vitest';
Expand Down Expand Up @@ -217,7 +218,8 @@ const namespace = { name: 'harness', storage: undefined };
// The harness is a module other suites import, so guard its own contract
// tests: registering them again inside every importer would run the same
// three cases five times and bury each suite's real subject.
if (expect.getState().testPath === fileURLToPath(import.meta.url)) {
const testPath = expect.getState().testPath;
if (testPath && normalize(testPath) === fileURLToPath(import.meta.url)) {
describe('asynchronous store harness', () => {
it('answers every port method with a promise, never synchronously', async () => {
const stores = createAsyncStores();
Expand Down
46 changes: 31 additions & 15 deletions external/agenetes/packages/agenetes/src/turn-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,35 @@ import {
import type { AgentTurn, Namespace } from '@agenetes/protocol';

let tmp: string;
const fileStores: FileTurnStore[] = [];
const namespaces: Namespace[] = [];

beforeEach(() => {
fileStores.length = 0;
namespaces.length = 0;
tmp = mkdtempSync(path.join(tmpdir(), 'agenetes-turnstore-'));
});

afterEach(() => {
for (const store of fileStores) {
for (const namespace of namespaces) store.close(namespace);
}
rmSync(tmp, { recursive: true, force: true });
});

const ns = (name: string): Namespace => ({
name,
storage: { root: path.join(tmp, name) },
});
const ns = (name: string): Namespace => {
const namespace = { name, storage: { root: path.join(tmp, name) } };
namespaces.push(namespace);
return namespace;
};

const fileStore = (
...args: ConstructorParameters<typeof FileTurnStore>
): FileTurnStore => {
const store = new FileTurnStore(...args);
fileStores.push(store);
return store;
};

const turn = (text: string): AgentTurn => ({
request: { type: 'user_text', content: `q:${text}` },
Expand All @@ -64,7 +80,7 @@ const turnDatabasePath = (namespace: Namespace): string =>
// stays a faithful twin of the in-memory default.
describe.each<[string, () => TurnStore]>([
['InMemoryTurnStore', () => new InMemoryTurnStore()],
['FileTurnStore', () => new FileTurnStore()],
['FileTurnStore', () => fileStore()],
])('%s — TurnStore contract', (_name, make) => {
it('appends and lists folded turns in fold order', () => {
const store = make();
Expand Down Expand Up @@ -236,21 +252,21 @@ describe.each<[string, () => TurnStore]>([
describe('FileTurnStore — on-disk specifics', () => {
it('persists to the Space-owned turns.sqlite and survives a restart', () => {
const n = ns('canvas-1');
const first = new FileTurnStore();
const first = fileStore();
first.append(n, 'thread-1', persisted('a', 1, 3));
first.append(n, 'thread-1', persisted('b', 4, 7));
expect(existsSync(turnDatabasePath(n))).toBe(true);

// A fresh store reads the same on-disk log (restart-surviving).
const restarted = new FileTurnStore();
const restarted = fileStore();
expect(restarted.list(n, 'thread-1').map((p) => p.seqEnd)).toEqual([3, 7]);
expect(restarted.count(n, 'thread-1')).toBe(2);
expect(restarted.fence(n, 'thread-1')).toBe(7);
});

it('closes the Space-owned database before its directory moves', () => {
const n = ns('canvas-1');
const store = new FileTurnStore();
const store = fileStore();
store.append(n, 'thread-1', persisted('a', 1, 3));

store.close(n);
Expand All @@ -266,7 +282,7 @@ describe('FileTurnStore — on-disk specifics', () => {

it('tolerates a corrupt tail line without bricking the read', () => {
const n = ns('canvas-1');
const store = new FileTurnStore();
const store = fileStore();
store.append(n, 'thread-1', persisted('a', 1, 3));
// Append a malformed line directly, as a torn write would leave behind.
appendFileSync(turnFilePath(n, 'thread-1'), '{ not json\n');
Expand All @@ -280,29 +296,29 @@ describe('FileTurnStore — on-disk specifics', () => {
it('replace() writes the target database and a fresh store instance reads it back', () => {
const source = ns('canvas-1');
const target = ns('canvas-2');
const writer = new FileTurnStore();
const writer = fileStore();
writer.append(source, 'thread-1', persisted('a', 1, 3));
writer.append(source, 'thread-1', persisted('b', 4, 7));
const snapshot = writer.list(source, 'thread-1');

writer.replace(target, 'thread-1', snapshot);
expect(existsSync(turnDatabasePath(target))).toBe(true);

const restarted = new FileTurnStore();
const restarted = fileStore();
expect(restarted.list(target, 'thread-1')).toEqual(snapshot);
expect(restarted.fence(target, 'thread-1')).toBe(7);
});

it('delete() removes the thread rows so a fresh store observes an empty log', () => {
const n = ns('canvas-1');
const writer = new FileTurnStore();
const writer = fileStore();
writer.append(n, 'thread-1', persisted('a', 1, 3));
expect(existsSync(turnDatabasePath(n))).toBe(true);

writer.delete(n, 'thread-1');
expect(existsSync(turnDatabasePath(n))).toBe(true);

const restarted = new FileTurnStore();
const restarted = fileStore();
expect(restarted.list(n, 'thread-1')).toEqual([]);
expect(restarted.count(n, 'thread-1')).toBe(0);
});
Expand All @@ -319,7 +335,7 @@ describe('FileTurnStore — on-disk specifics', () => {
{ flag: 'w' },
);

const store = new FileTurnStore();
const store = fileStore();
expect(store.list(n, 'thread-1').map((record) => record.seqEnd)).toEqual([
3, 7,
]);
Expand All @@ -333,7 +349,7 @@ describe('FileTurnStore — on-disk specifics', () => {
mkdirSync(path.dirname(source), { recursive: true });
writeFileSync(source, `${JSON.stringify(persisted('a', 1, 3))}\n`);
let fail = true;
const store = new FileTurnStore({
const store = fileStore({
rename: (from, to) => {
if (fail) {
fail = false;
Expand Down
3 changes: 2 additions & 1 deletion external/agenetes/packages/agenetes/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
"rootDir": "./src",
"types": ["node"]
},
"include": ["src"],
"exclude": ["**/*.test.ts"]
Expand Down
2 changes: 1 addition & 1 deletion external/agenetes/packages/agent-profile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
},
"devDependencies": {
"@types/node": "^22.15.0",
"typescript": "^5.9.0",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
}
}
3 changes: 2 additions & 1 deletion external/agenetes/packages/agent-profile/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
"rootDir": "./src",
"types": ["node"]
},
"include": ["src"],
"exclude": ["**/*.test.ts"]
Expand Down
2 changes: 1 addition & 1 deletion external/agenetes/packages/agentlet-gateway/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"devDependencies": {
"@types/node": "^22.15.0",
"@types/ws": "^8.5.0",
"typescript": "^5.9.0",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
}
}
2 changes: 1 addition & 1 deletion external/agenetes/packages/agentlet-host/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"devDependencies": {
"@types/ws": "^8.5.0",
"fastify": "^5.12.5",
"typescript": "^5.9.0",
"typescript": "^6.0.3",
"vitest": "^4.1.11",
"ws": "^8.18.0"
}
Expand Down
2 changes: 1 addition & 1 deletion external/agenetes/packages/pi-driver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"zod": "^4.3.6"
},
"devDependencies": {
"typescript": "^5.9.0",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
}
}
2 changes: 1 addition & 1 deletion external/agenetes/packages/protocol/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@
"zod": "^4.3.6"
},
"devDependencies": {
"typescript": "^5.9.0"
"typescript": "^6.0.3"
}
}
2 changes: 1 addition & 1 deletion external/agenetes/packages/runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@
"@agenetes/protocol": "workspace:*"
},
"devDependencies": {
"typescript": "^5.9.0"
"typescript": "^6.0.3"
}
}
2 changes: 1 addition & 1 deletion external/agentlet/packages/local/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@
},
"devDependencies": {
"@types/ws": "^8.5.0",
"typescript": "^5.9.0"
"typescript": "^6.0.3"
}
}
11 changes: 8 additions & 3 deletions external/agentlet/packages/local/tests/harnesses.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { join } from 'node:path'
import type { HarnessDiscoveryResult, JsonRpcMessage } from '@agentlet/protocol'
import { ServerMethods } from '@agentlet/protocol'
Expand Down Expand Up @@ -28,6 +28,10 @@ import { Agentlet } from '../src/agentlet.js'
import { AgentProcess } from '../src/agent-process.js'
import { Logger } from '../src/logger.js'

afterEach(() => {
vi.unstubAllEnvs()
})

beforeEach(() => {
vi.clearAllMocks()
mocks.platform.mockReturnValue('linux')
Expand Down Expand Up @@ -98,15 +102,16 @@ describe('trusted harness discovery', () => {
expect(harnesses[0]?.version).toBeUndefined()
})

it('supports Windows native executables and probes through the platform shell', async () => {
it.each([undefined, 'C:\\Windows\\System32\\cmd.exe'])('supports Windows native executables with ComSpec=%s', async (shell) => {
vi.stubEnv('ComSpec', shell)
mocks.platform.mockReturnValue('win32')
mocks.probe.mockImplementation(async (file: string, args: string[]) => ({
stdout: file === 'where.exe' ? `C:\\Tools\\${args[0]}.exe\r\nD:\\Other\\${args[0]}.exe\r\n` : '2.0\r\n',
}))
expect((await discoverHarnesses()).harnesses[0]).toMatchObject({
status: 'ready', version: '2.0',
})
expect(mocks.probe).toHaveBeenCalledWith('cmd.exe', ['/d', '/s', '/c', 'copilot --version'], expect.objectContaining({ shell: false }))
expect(mocks.probe).toHaveBeenCalledWith(shell ?? 'cmd.exe', ['/d', '/s', '/c', 'copilot --version'], expect.objectContaining({ shell: false }))
})

it.each(['cmd', 'CMD', 'bat', 'BAT'])(
Expand Down
2 changes: 1 addition & 1 deletion external/agentlet/packages/protocol/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@
"lint": "tsc --noEmit"
},
"devDependencies": {
"typescript": "^5.9.0"
"typescript": "^6.0.3"
}
}
Loading
Loading