merge: integrate remote main
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { findPackageJSON } from 'node:module';
|
||||
import path from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
|
||||
@@ -14,12 +15,36 @@ const runtimeRoot = runtimeRootFromArgs(process.argv.slice(2));
|
||||
const runtimeModule = (...segments) => pathToFileURL(path.join(runtimeRoot, ...segments)).href;
|
||||
const runtimePackageUrl = pathToFileURL(path.join(runtimeRoot, 'package.json')).href;
|
||||
|
||||
async function runtimePackageModule(packageName) {
|
||||
const packageJsonPath = findPackageJSON(packageName, runtimePackageUrl);
|
||||
if (!packageJsonPath) throw new Error(`Runtime package is unavailable: ${packageName}`);
|
||||
const packageRoot = path.dirname(packageJsonPath);
|
||||
const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8'));
|
||||
const rootExport = packageJson.exports?.['.'];
|
||||
const entry = (
|
||||
typeof rootExport === 'string'
|
||||
? rootExport
|
||||
: rootExport?.import
|
||||
) ?? packageJson.module ?? packageJson.main;
|
||||
if (typeof entry !== 'string' || !entry.trim()) {
|
||||
throw new Error(`Runtime package import entry is unavailable: ${packageName}`);
|
||||
}
|
||||
const entryPath = path.resolve(packageRoot, entry);
|
||||
const entryRelative = path.relative(packageRoot, entryPath);
|
||||
if (!entryRelative || entryRelative === '..' || entryRelative.startsWith(`..${path.sep}`)
|
||||
|| path.isAbsolute(entryRelative)) {
|
||||
throw new Error(`Runtime package import entry escapes its package: ${packageName}`);
|
||||
}
|
||||
return pathToFileURL(entryPath).href;
|
||||
}
|
||||
|
||||
const outputGuard = await import(runtimeModule('dist', 'core', 'output-guard.js'));
|
||||
outputGuard.takeOverStdout();
|
||||
const piAiModule = await runtimePackageModule('@earendil-works/pi-ai');
|
||||
|
||||
const [pi, piAi, httpDispatcher, jsonEvents, jsonl, themeModule, shellModule] = await Promise.all([
|
||||
import(runtimeModule('dist', 'index.js')),
|
||||
import(import.meta.resolve('@earendil-works/pi-ai', runtimePackageUrl)),
|
||||
import(piAiModule),
|
||||
import(runtimeModule('dist', 'core', 'http-dispatcher.js')),
|
||||
import(runtimeModule('dist', 'modes', 'json-event.js')),
|
||||
import(runtimeModule('dist', 'modes', 'rpc', 'jsonl.js')),
|
||||
@@ -46,6 +71,7 @@ function configureNetwork(settingsManager) {
|
||||
|
||||
const SERVER_CHANNEL = '@makelore/server';
|
||||
const PROTOCOL_VERSION = 1;
|
||||
const PROJECT_WRITE_LEASE_TOOL_NAMES = new Set(['bash', 'edit', 'write']);
|
||||
const threads = new Map();
|
||||
const openingThreads = new Set();
|
||||
let shuttingDown = false;
|
||||
@@ -106,6 +132,14 @@ function referencedEnvironmentNames(value) {
|
||||
return names;
|
||||
}
|
||||
|
||||
function serializeProjectWriteLeaseTools(session) {
|
||||
session.agent.state.tools = session.agent.state.tools.map((tool) => (
|
||||
PROJECT_WRITE_LEASE_TOOL_NAMES.has(tool.name) && tool.executionMode !== 'sequential'
|
||||
? { ...tool, executionMode: 'sequential' }
|
||||
: tool
|
||||
));
|
||||
}
|
||||
|
||||
async function credentialStoreFor(options, providerId) {
|
||||
const models = JSON.parse(await readFile(path.join(options.configDir, 'models.json'), 'utf8'));
|
||||
const provider = record(record(models)?.providers)?.[providerId];
|
||||
@@ -370,6 +404,12 @@ class AgentThread {
|
||||
|
||||
async rebind() {
|
||||
const session = this.runtime.session;
|
||||
// Pi prepares every tool_call hook before starting a parallel tool batch.
|
||||
// Makelore acquires its project write lease in that hook and releases it in
|
||||
// tool_result, so two parallel mutation tools would otherwise wait on each
|
||||
// other before either command can start. Mark only the built-in mutation
|
||||
// tools sequential; read-only batches retain Pi's parallel execution.
|
||||
serializeProjectWriteLeaseTools(session);
|
||||
await session.bindExtensions({
|
||||
uiContext: this.createExtensionUiContext(),
|
||||
mode: 'rpc',
|
||||
@@ -428,7 +468,14 @@ class AgentThread {
|
||||
this.emit(success(id, 'prompt'));
|
||||
},
|
||||
}).catch((error) => {
|
||||
if (!accepted) this.emit(failure(id, 'prompt', error));
|
||||
if (!accepted) {
|
||||
this.emit(failure(id, 'prompt', error));
|
||||
return;
|
||||
}
|
||||
this.emit({
|
||||
type: 'makelore_thread_error',
|
||||
code: 'PROMPT_FAILED_AFTER_ACCEPTANCE',
|
||||
});
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
@@ -454,6 +501,7 @@ class AgentThread {
|
||||
thinkingLevel: session.thinkingLevel,
|
||||
isStreaming: session.isStreaming,
|
||||
isCompacting: session.isCompacting,
|
||||
retryAttempt: session.retryAttempt,
|
||||
steeringMode: session.steeringMode,
|
||||
followUpMode: session.followUpMode,
|
||||
sessionFile: session.sessionFile,
|
||||
|
||||
Reference in New Issue
Block a user