feat: add Pi subagent scheduler and child runtime
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
import path from 'node:path';
|
||||
import { atomicWriteText } from '../../../coding-projects/atomic-json';
|
||||
|
||||
export const MAKELORE_PI_EXTENSION_VERSION = 1;
|
||||
export const MAKELORE_PI_EXTENSION_VERSION = 2;
|
||||
export const MAKELORE_PI_EXTENSION_FILENAME = `makelore-runtime-v${MAKELORE_PI_EXTENSION_VERSION}.mjs`;
|
||||
|
||||
const BUNDLE_SOURCE = String.raw`
|
||||
import { readFile } from 'node:fs/promises';
|
||||
|
||||
const MUTATION_TOOLS = new Set(['bash', 'edit', 'write']);
|
||||
const WORKER_ROLE = process.env.MAKELORE_PI_WORKER_ROLE || 'parent';
|
||||
const leases = new Map();
|
||||
|
||||
async function runtimeContext() {
|
||||
@@ -32,6 +33,48 @@ async function bridge(action, body, signal) {
|
||||
return result;
|
||||
}
|
||||
|
||||
async function bridgeStream(action, body, signal, onUpdate) {
|
||||
const context = await runtimeContext();
|
||||
const response = await fetch(process.env.MAKELORE_PI_BRIDGE_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
authorization: 'Bearer ' + process.env.MAKELORE_PI_WORKER_TOKEN,
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ ...context, action, ...body }),
|
||||
signal,
|
||||
});
|
||||
if (!response.ok) {
|
||||
const result = await response.json().catch(() => ({}));
|
||||
throw new Error(result.error || 'Makelore runtime bridge rejected the request');
|
||||
}
|
||||
if (!response.body) throw new Error('Makelore runtime bridge returned no stream');
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buffered = '';
|
||||
let details;
|
||||
while (true) {
|
||||
const { value, done } = await reader.read();
|
||||
buffered += decoder.decode(value || new Uint8Array(), { stream: !done });
|
||||
let newline = buffered.indexOf('\n');
|
||||
while (newline >= 0) {
|
||||
const line = buffered.slice(0, newline);
|
||||
buffered = buffered.slice(newline + 1);
|
||||
if (line) {
|
||||
const item = JSON.parse(line);
|
||||
if (item.details?.schema === 'subagent.v1') {
|
||||
details = item.details;
|
||||
onUpdate?.({ content: [], details });
|
||||
}
|
||||
}
|
||||
newline = buffered.indexOf('\n');
|
||||
}
|
||||
if (done) break;
|
||||
}
|
||||
if (!details) throw new Error('Makelore subagent stream returned no details');
|
||||
return details;
|
||||
}
|
||||
|
||||
async function releaseLease(toolCallId) {
|
||||
const leaseId = leases.get(toolCallId);
|
||||
if (!leaseId) return;
|
||||
@@ -44,7 +87,7 @@ async function releaseAll() {
|
||||
}
|
||||
|
||||
export default function makeloreRuntime(pi) {
|
||||
pi.registerTool({
|
||||
if (WORKER_ROLE === 'parent') pi.registerTool({
|
||||
name: 'ask_user',
|
||||
label: 'Ask user',
|
||||
description: 'Ask the user for a selection, confirmation, short input, or editor text.',
|
||||
@@ -77,6 +120,52 @@ export default function makeloreRuntime(pi) {
|
||||
},
|
||||
});
|
||||
|
||||
if (WORKER_ROLE === 'parent') pi.registerTool({
|
||||
name: 'subagent',
|
||||
label: 'Subagent',
|
||||
description: 'Dispatch one or more managed project Agents in single, parallel, or chain mode.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['mode', 'tasks'],
|
||||
properties: {
|
||||
mode: { type: 'string', enum: ['single', 'parallel', 'chain'] },
|
||||
tasks: {
|
||||
type: 'array', minItems: 1, maxItems: 8,
|
||||
items: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['agentId', 'task', 'toolProfile'],
|
||||
properties: {
|
||||
agentId: { type: 'string' },
|
||||
task: { type: 'string' },
|
||||
toolProfile: { type: 'string', enum: ['read-only', 'coding'] },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async execute(toolCallId, params, signal, onUpdate) {
|
||||
const details = await bridgeStream(
|
||||
'subagent.dispatch',
|
||||
{ resourceId: toolCallId, request: params },
|
||||
signal,
|
||||
onUpdate,
|
||||
);
|
||||
const complete = details.tasks.filter((task) => task.status === 'complete').length;
|
||||
const failed = details.tasks.length - complete;
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: failed === 0
|
||||
? complete + ' subagent task(s) completed'
|
||||
: complete + ' completed; ' + failed + ' did not complete',
|
||||
}],
|
||||
details,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
pi.on('tool_call', async (event, ctx) => {
|
||||
if (!MUTATION_TOOLS.has(event.toolName)) return;
|
||||
ctx.ui.setStatus('makelore.write-lease', '等待项目写入');
|
||||
|
||||
Reference in New Issue
Block a user