Add 5 pi extensions: pi-subagents, pi-crew, rpiv-pi, pi-interactive-shell, pi-intercom
This commit is contained in:
104
extensions/pi-crew/src/extension/team-tool/respond.ts
Normal file
104
extensions/pi-crew/src/extension/team-tool/respond.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
|
||||
import { withRunLockSync } from "../../state/locks.ts";
|
||||
import { loadRunManifestById, saveRunTasks, updateRunStatus } from "../../state/state-store.ts";
|
||||
import { appendEvent } from "../../state/event-log.ts";
|
||||
import { appendMailboxMessage } from "../../state/mailbox.ts";
|
||||
import { saveCrewAgents, recordFromTask } from "../../runtime/crew-agent-records.ts";
|
||||
import { logInternalError } from "../../utils/internal-error.ts";
|
||||
import type { PiTeamsToolResult } from "../tool-result.ts";
|
||||
import { result, type TeamContext } from "./context.ts";
|
||||
|
||||
/**
|
||||
* Handle `respond` action: send a message to a waiting (interactive) task.
|
||||
* The task must be in "waiting" status. The message is stored in the task's
|
||||
* mailbox and the task is re-queued for durable scheduler resume.
|
||||
*/
|
||||
export function handleRespond(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
|
||||
if (!params.runId) return result("Respond requires runId.", { action: "respond", status: "error" }, true);
|
||||
if (!params.message && !params.taskId) return result("Respond requires taskId and/or message.", { action: "respond", status: "error" }, true);
|
||||
|
||||
const loaded = loadRunManifestById(ctx.cwd, params.runId);
|
||||
if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "respond", status: "error" }, true);
|
||||
|
||||
return withRunLockSync(loaded.manifest, () => {
|
||||
const fresh = loadRunManifestById(ctx.cwd, params.runId!);
|
||||
if (!fresh) return result(`Run '${params.runId}' not found.`, { action: "respond", status: "error" }, true);
|
||||
const foreignRun = typeof fresh.manifest.ownerSessionId === "string" && fresh.manifest.ownerSessionId !== ctx.sessionId;
|
||||
if (foreignRun) return result(`Run ${fresh.manifest.runId} belongs to another session; not responding.`, { action: "respond", status: "error", runId: fresh.manifest.runId }, true);
|
||||
|
||||
const taskId = params.taskId;
|
||||
const message = params.message ?? "";
|
||||
|
||||
const targetTasks = taskId
|
||||
? fresh.tasks.filter((t) => t.id === taskId && t.status === "waiting")
|
||||
: fresh.tasks.filter((t) => t.status === "waiting");
|
||||
|
||||
if (targetTasks.length === 0) {
|
||||
const existing = taskId ? fresh.tasks.find((t) => t.id === taskId) : undefined;
|
||||
const hint = " Use api operation=follow-up-agent for continuation prompts or api operation=steer-agent to interrupt active work.";
|
||||
return result(
|
||||
(taskId
|
||||
? existing
|
||||
? `Task '${taskId}' is ${existing.status}, not waiting.`
|
||||
: `Task '${taskId}' not found.`
|
||||
: `No waiting tasks in run ${fresh.manifest.runId}.`) + hint,
|
||||
{ action: "respond", status: "error", runId: fresh.manifest.runId },
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
const resumed = new Set(targetTasks.map((t) => t.id));
|
||||
const mailboxIds: string[] = [];
|
||||
for (const task of targetTasks) {
|
||||
const mailbox = appendMailboxMessage(fresh.manifest, {
|
||||
direction: "inbox",
|
||||
from: "leader",
|
||||
to: task.id,
|
||||
taskId: task.id,
|
||||
body: message || "(resume)",
|
||||
kind: "response",
|
||||
priority: "normal",
|
||||
deliveryMode: "next_turn",
|
||||
data: { action: "respond", kind: "response" },
|
||||
});
|
||||
mailboxIds.push(mailbox.id);
|
||||
}
|
||||
|
||||
// Re-queue waiting tasks so durable scheduler/resume can pick them up again.
|
||||
const updatedTasks = fresh.tasks.map((task) => {
|
||||
if (!resumed.has(task.id)) return task;
|
||||
return {
|
||||
...task,
|
||||
status: "queued" as const,
|
||||
startedAt: undefined,
|
||||
finishedAt: undefined,
|
||||
error: undefined,
|
||||
adaptive: {
|
||||
...task.adaptive,
|
||||
phase: "resumed",
|
||||
task: message || task.adaptive?.task || "",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
saveRunTasks(fresh.manifest, updatedTasks);
|
||||
let manifest = fresh.manifest;
|
||||
if (manifest.status === "blocked" || manifest.status === "completed" || manifest.status === "failed" || manifest.status === "cancelled") {
|
||||
manifest = updateRunStatus(manifest, "running", `Resumed ${resumed.size} waiting task(s).`);
|
||||
}
|
||||
for (const taskId of resumed) {
|
||||
appendEvent(manifest.eventsPath, { type: "task.resumed", runId: manifest.runId, taskId, message: message || "Task re-queued after respond.", data: { mailboxIds } });
|
||||
}
|
||||
try {
|
||||
saveCrewAgents(fresh.manifest, updatedTasks.map((task) => recordFromTask(fresh.manifest, task, "child-process")));
|
||||
} catch (error) {
|
||||
logInternalError("team-tool.handleRespond.crewAgents", error, `runId=${fresh.manifest.runId}`);
|
||||
}
|
||||
|
||||
const resumedIds = targetTasks.map((t) => t.id);
|
||||
return result(
|
||||
`Resumed ${resumedIds.length} waiting task(s): ${resumedIds.join(", ")}. Message: ${message || "(no message)"}`,
|
||||
{ action: "respond", status: "ok", runId: fresh.manifest.runId, resumedIds, mailboxIds },
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user