merge: integrate packaged Pi Agent Server fix
This commit is contained in:
@@ -0,0 +1,67 @@
|
|||||||
|
# Task: Diagnose local programming runtime unavailable
|
||||||
|
|
||||||
|
## Identity
|
||||||
|
|
||||||
|
- Task ID: 20260901-local-runtime-unavailable-8b42c7f1
|
||||||
|
- Mode: Feature
|
||||||
|
- Branch: codex/20260901-local-runtime-unavailable-8b42c7f1-local-runtime-unavailable-8b42c7f1
|
||||||
|
- Worktree: D:\Datas\OthersProjects\makelore-worktrees\local-runtime-unavailable-8b42c7f1
|
||||||
|
- Base commit: 7f0e9310a7f394249fab3cadc5c82d70f91cbfeb
|
||||||
|
- Owner: codex
|
||||||
|
- Status: Ready for Integration
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- Reproduce the current Makelore Code state that renders `本地编程运行时暂时不可用。` and identify the first failing Renderer, Host API, Pi runtime, packaging, or Provider boundary.
|
||||||
|
- Build a deterministic, agent-runnable feedback loop for the exact visible symptom before ranking or testing root-cause hypotheses.
|
||||||
|
- Correlate current source and focused tests with privacy-safe local runtime evidence where available.
|
||||||
|
- Implement the user-authorized minimal product fix for the confirmed packaged-layout Pi AI ESM resolution failure.
|
||||||
|
- Add a focused regression at the installed sibling-resource-layout seam, then verify the original process path plus the repository's required checks.
|
||||||
|
|
||||||
|
## Intent And Constraints
|
||||||
|
|
||||||
|
- Treat the visible sentence as a generic safe projection, not as proof of a Pi process failure.
|
||||||
|
- Preserve ADR-006: Pi `0.84.2` remains the sole production runtime; do not add an OpenCode fallback, dual runtime, compatibility layer, or automatic replay.
|
||||||
|
- Keep Renderer access behind `/api/coding/*`; runtime, Provider credentials, recovery, and raw diagnostics remain Electron Main-owned.
|
||||||
|
- Redact credentials, headers, prompts, session content, account identifiers, and complete user project paths from reported evidence.
|
||||||
|
- Distinguish confirmed facts, evidence-supported inferences, and unverified assumptions. The historical Host-token startup defect is a regression lead, not an assumed cause.
|
||||||
|
- Keep package resolution anchored to the explicit Main-owned `runtimeRoot`; development and packaged layouts must both continue to work without hard-coded layout branches or a compatibility layer.
|
||||||
|
- Work only in this isolated task worktree and do not modify peer worktrees or the occupied local `main` worktree.
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
|
||||||
|
- Confirmed that the installed Makelore application reaches the generic `CODING_RUNTIME_UNAVAILABLE` projection only after the Pi worker fails during `open`.
|
||||||
|
- The installed Agent Server imports `@earendil-works/pi-ai` with `import.meta.resolve(specifier, runtimePackageUrl)`. In the embedded Node.js `v24.18.1` process, the second `parentURL` argument is not honored unless `--experimental-import-meta-resolve` is enabled, so resolution starts beside `resources/resources/pi-agent-server.mjs` instead of the sibling `resources/pi-runtime/node_modules` tree.
|
||||||
|
- The dependency is present and healthy: the installed Agent Server matches repository source byte-for-byte, both `@earendil-works/pi-coding-agent` and `@earendil-works/pi-ai` are version `0.84.2`, and the expected ESM export exists.
|
||||||
|
- Reproduced the installed startup failure twice with the exact executable, script, runtime root, working directory, and environment shape. Both attempts exited with code `1` and `ERR_MODULE_NOT_FOUND` before producing protocol output.
|
||||||
|
- A one-variable differential adding `--experimental-import-meta-resolve` made the same process exit cleanly without the module error. Importing the verified absolute `pi-runtime/node_modules/@earendil-works/pi-ai/dist/index.js` URL also succeeded without the experimental flag.
|
||||||
|
- Added `--experimental-import-meta-resolve` only to the Main-owned Agent Server child process. This activates the existing `runtimePackageUrl` parent argument and preserves standard ESM package resolution across both the development layout and packaged `pi-runtime` closure.
|
||||||
|
- Added a real-process regression that places `pi-agent-server.mjs` under the installed `resources/resources` sibling layout, exposes the real Pi runtime and Pi AI package through a temporary production-shaped root, and starts it with the project's Electron executable.
|
||||||
|
- The regression failed before the fix with the same `ERR_MODULE_NOT_FOUND` / `PI_RPC_EXITED` chain and passed after the fix. No runtime bundle layout, dependency version, Provider behavior, Renderer contract, installed application file, user data, or peer task content was changed.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- Current startup-log signal loop: found the missing `@earendil-works/pi-ai` module plus two `PI_WORKER_STOPPED` / `open_failure` events; verdict `RED` as expected for the reported failure.
|
||||||
|
- Exact installed-process loop: two independent launches reproduced `ExitCode=1`, `ModuleNotFound=true`, `MissingPiAi=true`, and zero protocol bytes within five seconds.
|
||||||
|
- Resolver differential: the same installed process with only `--experimental-import-meta-resolve` added produced no resolver error.
|
||||||
|
- Absolute-path differential: importing the fixed runtime-root ESM entry through `pathToFileURL(...)` succeeded in the same embedded Node.js process without the experimental flag.
|
||||||
|
- Artifact comparison: installed `pi-agent-server.mjs` equals repository source; installed Pi package versions and exports match the expected `0.84.2` runtime closure.
|
||||||
|
- The existing real-process unit test was inspected and does not cover this boundary because it runs the server from the repository layout, where ancestor lookup can reach the root `node_modules`; the installed sibling `resources/resources` and `resources/pi-runtime` layout is not exercised.
|
||||||
|
- Red regression: `pnpm exec vitest run tests/unit/pi-agent-server-process-real.test.ts -t "boots from the packaged sibling resource layout" --maxWorkers=1` failed at `pi-agent-server.mjs:22` with `ERR_MODULE_NOT_FOUND` and `PI_RPC_EXITED` before the product change.
|
||||||
|
- Green regression: the same command passed against Electron Node after the Agent Server launch flag was added.
|
||||||
|
- Focused verification: 4 relevant test files, 24 tests passed, covering Agent Server behavior, composition paths, Pi runtime bundling, and product artifact rules.
|
||||||
|
- Existing real-process behavior: both tests in `pi-agent-server-process-real.test.ts` passed, including two logical Conversation threads, process reuse, invalidation, and restart.
|
||||||
|
- Full unit suite: 215 test files passed; 1,761 tests passed and 2 existing conditional tests were skipped.
|
||||||
|
- `pnpm run typecheck`: passed. Final changed-file ESLint: passed.
|
||||||
|
- `pnpm run lint:check`: passed with 0 errors and 5 pre-existing unrelated React warnings.
|
||||||
|
- `pnpm run build:vite`: passed for Renderer, Electron Main, Preload, and utility worker; only existing bundle/chunk warnings were emitted.
|
||||||
|
- `git diff --check`: passed; no debug instrumentation or generated artifact is tracked.
|
||||||
|
|
||||||
|
## Follow-ups
|
||||||
|
|
||||||
|
- The currently installed application predates this source change. After integration, rebuild the Windows artifact, run `verify:artifact:win`, `verify:artifact:pi`, and the final packaged Pi proof, then install it before performing the first-conversation UI acceptance.
|
||||||
|
- When the pinned Electron/Node runtime is upgraded, retain the packaged-layout regression; remove the launch flag only after the new embedded Node accepts the `import.meta.resolve` parent argument without it.
|
||||||
|
|
||||||
|
## Promotion Candidates
|
||||||
|
|
||||||
|
- Target: `current-state` and the Pi release evidence index. Proposal: require an Electron Node Agent Server initialization proof from the installed sibling resource layout, not only source-tree process tests. Evidence: the new layout test reproduced the exact installed `ERR_MODULE_NOT_FOUND` before the fix and passed after enabling the resolver parent URL; 24 focused tests, the full unit suite, typecheck, lint, and production build passed. Future impact: prevents source-root ancestor `node_modules` from masking final-artifact ESM resolution regressions. Semantic conflicts: none with ADR-006 or the existing Pi closure requirement. Human confirmation: not required to integrate the evidence rule; final package installation/release remains an operator gate.
|
||||||
@@ -514,7 +514,13 @@ export class PiAgentServerProcess {
|
|||||||
const generation = ++this.generation;
|
const generation = ++this.generation;
|
||||||
const child = spawn(
|
const child = spawn(
|
||||||
this.options.executablePath,
|
this.options.executablePath,
|
||||||
[this.options.serverPath, '--runtime-root', this.options.runtimeRoot],
|
[
|
||||||
|
// Enables the parent URL used to resolve dependencies from the packaged Pi runtime root.
|
||||||
|
'--experimental-import-meta-resolve',
|
||||||
|
this.options.serverPath,
|
||||||
|
'--runtime-root',
|
||||||
|
this.options.runtimeRoot,
|
||||||
|
],
|
||||||
{
|
{
|
||||||
cwd: this.options.runtimeRoot,
|
cwd: this.options.runtimeRoot,
|
||||||
env: buildPiWorkerEnvironment(this.options.configDir),
|
env: buildPiWorkerEnvironment(this.options.configDir),
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
// @vitest-environment node
|
// @vitest-environment node
|
||||||
|
|
||||||
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
|
import { copyFile, mkdtemp, mkdir, realpath, rm, symlink, writeFile } from 'node:fs/promises';
|
||||||
import { createServer, type ServerResponse } from 'node:http';
|
import { createServer, type ServerResponse } from 'node:http';
|
||||||
|
import { createRequire } from 'node:module';
|
||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { afterEach, describe, expect, it } from 'vitest';
|
import { afterEach, describe, expect, it } from 'vitest';
|
||||||
@@ -11,6 +12,36 @@ import { MAKELORE_DEFAULT_LANGUAGE_PROMPT } from '../../electron/coding-runtime/
|
|||||||
import type { PiWorkerProcessOptions } from '../../electron/coding-runtime/pi/worker-process';
|
import type { PiWorkerProcessOptions } from '../../electron/coding-runtime/pi/worker-process';
|
||||||
|
|
||||||
const roots: string[] = [];
|
const roots: string[] = [];
|
||||||
|
const electronExecutable = createRequire(path.resolve('package.json'))('electron') as string;
|
||||||
|
|
||||||
|
async function materializePackagedAgentServerLayout(root: string): Promise<{
|
||||||
|
configDir: string;
|
||||||
|
runtimeRoot: string;
|
||||||
|
serverPath: string;
|
||||||
|
}> {
|
||||||
|
const resourcesPath = path.join(root, 'artifact', 'resources');
|
||||||
|
const serverPath = path.join(resourcesPath, 'resources', 'pi-agent-server.mjs');
|
||||||
|
const runtimeRoot = path.join(resourcesPath, 'pi-runtime');
|
||||||
|
const configDir = path.join(root, 'config');
|
||||||
|
const piAiRoot = path.join(runtimeRoot, 'node_modules', '@earendil-works', 'pi-ai');
|
||||||
|
const [sourceRuntimeRoot, sourcePiAiRoot] = await Promise.all([
|
||||||
|
realpath(path.resolve('node_modules/@earendil-works/pi-coding-agent')),
|
||||||
|
realpath(path.resolve('node_modules/@earendil-works/pi-ai')),
|
||||||
|
]);
|
||||||
|
await Promise.all([
|
||||||
|
mkdir(path.dirname(serverPath), { recursive: true }),
|
||||||
|
mkdir(path.dirname(piAiRoot), { recursive: true }),
|
||||||
|
mkdir(configDir, { recursive: true }),
|
||||||
|
]);
|
||||||
|
const directoryLinkType = process.platform === 'win32' ? 'junction' : 'dir';
|
||||||
|
await Promise.all([
|
||||||
|
copyFile(path.resolve('resources/pi-agent-server.mjs'), serverPath),
|
||||||
|
copyFile(path.join(sourceRuntimeRoot, 'package.json'), path.join(runtimeRoot, 'package.json')),
|
||||||
|
symlink(path.join(sourceRuntimeRoot, 'dist'), path.join(runtimeRoot, 'dist'), directoryLinkType),
|
||||||
|
symlink(sourcePiAiRoot, piAiRoot, directoryLinkType),
|
||||||
|
]);
|
||||||
|
return { configDir, runtimeRoot, serverPath };
|
||||||
|
}
|
||||||
|
|
||||||
async function startHeldProvider(): Promise<{
|
async function startHeldProvider(): Promise<{
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
@@ -85,6 +116,23 @@ afterEach(async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Pi Agent Server real process', () => {
|
describe('Pi Agent Server real process', () => {
|
||||||
|
it('boots from the packaged sibling resource layout', async () => {
|
||||||
|
const root = await mkdtemp(path.join(tmpdir(), 'makelore-pi-agent-server-layout-'));
|
||||||
|
roots.push(root);
|
||||||
|
const layout = await materializePackagedAgentServerLayout(root);
|
||||||
|
const server = new PiAgentServerProcess({
|
||||||
|
executablePath: electronExecutable,
|
||||||
|
...layout,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await expect(server.start()).resolves.toBeUndefined();
|
||||||
|
expect(server.processId).toBeTypeOf('number');
|
||||||
|
} finally {
|
||||||
|
await server.stop();
|
||||||
|
}
|
||||||
|
}, 10_000);
|
||||||
|
|
||||||
it('hosts isolated Conversation threads in one long-lived process', async () => {
|
it('hosts isolated Conversation threads in one long-lived process', async () => {
|
||||||
const root = await mkdtemp(path.join(tmpdir(), 'makelore-pi-agent-server-'));
|
const root = await mkdtemp(path.join(tmpdir(), 'makelore-pi-agent-server-'));
|
||||||
roots.push(root);
|
roots.push(root);
|
||||||
|
|||||||
Reference in New Issue
Block a user