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
6 changes: 3 additions & 3 deletions docs/plans/automatic-session-context-resilience.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ affectedPaths:
- "src/shared/session/request-dispatch.ts"
- "src/shared/session/background-tasks.ts"
- "src/tools/delegate-agent.ts"
- "src/tools/__tests__/delegate-agent.test.js"
- "src/tools/__tests__/delegate-agent.test.ts"
- "src/cmd/compact/index.ts"
- "src/cmd/compact/index.test.js"
- "src/ui/tui/runtime-adapter.ts"
Expand Down Expand Up @@ -334,7 +334,7 @@ shifts, migration or compatibility risk grows, or the Verification Plan no longe
- `src/tools/delegate-agent.ts`, `src/shared/session/background-tasks.ts`, and background task tests — map foreground
and background pause outcomes through their different existing settlement paths. Do not present partial output as
successful work.
- `src/tools/__tests__/delegate-agent.test.js` — verify delegated Agent cancellation and context pause preserve tool
- `src/tools/__tests__/delegate-agent.test.ts` — verify delegated Agent cancellation and context pause preserve tool
settlement, report `context_resilience_paused` deterministically, and respect Hosted Session compaction serialization.
- `src/cmd/compact/index.ts` — handle Runtime's `compaction_in_progress` result without reading success-only compaction
fields and display one concise retry-later message.
Expand Down Expand Up @@ -462,7 +462,7 @@ unless the complete public AgentSession contract has been verified; a partial ga
src/shared/session/agent-handler.test.ts src/shared/session/request-dispatch.test.ts
src/shared/session/background-tasks.test.ts src/shared/session/background-tasks-extra.test.ts
src/shared/session/segment-rollover.test.js src/shared/session/session-transcript-projection.test.js
src/tools/__tests__/delegate-agent.test.js src/cmd/compact/index.test.js
src/tools/__tests__/delegate-agent.test.ts src/cmd/compact/index.test.js
src/ui/tui/runtime-adapter.test.js src/acp/server.test.js src/ui/workspace/workspace-session-ux.test.tsx`.
Include new focused integration suites through the same runner. Never use direct `deno test`.
- Existing coverage: preserve manual/resume compaction, automatic-disabled behavior, image and Named Invocation context,
Expand Down
2 changes: 1 addition & 1 deletion scripts/bun-test-files.json
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,7 @@
"src/testing/workflow-binary-fixtures.test.ts",
"src/tools/__tests__/bash-fixture.test.ts",
"src/tools/__tests__/bash.test.ts",
"src/tools/__tests__/delegate-agent.test.js",
"src/tools/__tests__/delegate-agent.test.ts",
"src/tools/__tests__/delegate-bash-integration.test.ts",
"src/tools/__tests__/docs-file-tools.test.ts",
"src/tools/__tests__/edit.test.ts",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,48 +16,43 @@ import { createDelegateAgentTool, diffDelegatedChangeSnapshot, resolveDelegatedT
import { loadSubAgentDefinition } from "../../shared/session/subagent-definitions.ts";
import { SUBAGENTS } from "../../constants.js";

/**
* @typedef {Object} DelegateToolDetails
* @property {boolean} ok
* @property {"read" | "write"} mode
* @property {string} [role]
* @property {"read" | "write"} [requestedAuthority]
* @property {"read" | "write"} [effectiveAuthority]
* @property {"read" | "write"} [roleAuthorityCeiling]
* @property {string[]} [validRoles]
* @property {string[]} [tools]
* @property {string[]} [changedPaths]
* @property {boolean} [changeAttributionComplete]
* @property {boolean} [committedChangesDetected]
* @property {string} [error]
*/

/**
* @typedef {Object} DelegateToolResult
* @property {Array<{ type: string, text: string }>} content
* @property {DelegateToolDetails} details
* @property {boolean} [isError]
*/

/**
* @typedef {Object} ExecutableTool
* @property {(toolCallId: string, params: Record<string, unknown>, signal: AbortSignal, onUpdate: () => void, context: Record<string, unknown>) => Promise<DelegateToolResult>} execute
*/

/**
* @param {unknown} tool
* @param {Record<string, unknown>} params
* @param {AbortSignal} [signal]
* @returns {Promise<DelegateToolResult>}
*/
async function execute(tool, params, signal = new AbortController().signal) {
const executable = /** @type {ExecutableTool} */ (tool);
return await executable.execute("delegate-call", params, signal, () => {}, {});
import type { AgentMessage } from "@earendil-works/pi-agent-core";
import type { AssistantMessage, TextContent } from "@earendil-works/pi-ai";
import type { AgentToolResult, ExtensionToolContext } from "@earendil-works/pi-coding-agent";
import type { DelegateAgentDetails, DelegatedAgentSessionOptions, DelegationMode } from "../delegate-agent.ts";

type DelegateTool = ReturnType<typeof createDelegateAgentTool>;
interface DelegateToolResult extends Omit<AgentToolResult<DelegateAgentDetails>, "content"> {
content: TextContent[];
isError?: boolean;
}
type DelegatedLeaseState = ReturnType<HostedSession["getDelegatedAgentLeaseState"]>;

/** @returns {Promise<import('@earendil-works/pi-agent-core').AgentMessage[]>} */
function assistantDone() {
const message = /** @type {import('@earendil-works/pi-ai').AssistantMessage} */ ({
// The invalid-role case deliberately exercises the tool's runtime validation boundary.
interface DelegateTestParameters {
mode: DelegationMode;
role?: string;
brief: string;
}

async function execute(
tool: DelegateTool,
params: DelegateTestParameters,
signal = new AbortController().signal,
): Promise<DelegateToolResult> {
const executable = tool;
// These fixtures use no context fields and assert the tool's text-only results.
return (await executable.execute(
"delegate-call",
params,
signal,
() => {},
{} as ExtensionToolContext,
)) as DelegateToolResult;
}

function assistantDone(): Promise<AgentMessage[]> {
const message: AssistantMessage = {
role: "assistant",
api: "openai-completions",
provider: "test",
Expand All @@ -73,15 +68,11 @@ function assistantDone() {
},
stopReason: "stop",
timestamp: Date.now(),
});
};
return Promise.resolve([message]);
}

/**
* @param {string} cwd
* @param {string[]} args
*/
async function git(cwd, args) {
async function git(cwd: string, args: string[]) {
const output = await captureForegroundProcess("git", { cwd, args });
if (!output.success) throw new Error(new TextDecoder().decode(output.stderr));
return new TextDecoder().decode(output.stdout);
Expand Down Expand Up @@ -197,8 +188,7 @@ test("delegated agent prompt includes inherited repository context placeholders"
});

test("delegate_agent returns child output without inheriting workflow tools", async () => {
/** @type {Array<import('../delegate-agent.ts').DelegatedAgentSessionOptions>} */
const calls = [];
const calls: DelegatedAgentSessionOptions[] = [];
const hostedSession = new HostedSession({ id: "delegate-read", cwd: getCwd() });
const tool = createDelegateAgentTool({
hostedSession,
Expand Down Expand Up @@ -235,11 +225,9 @@ test("delegate_agent returns child output without inheriting workflow tools", as
});

test("delegate_agent applies verification-adversary read-only role ceiling", async () => {
/** @type {Array<import('../delegate-agent.ts').DelegatedAgentSessionOptions>} */
const calls = [];
const calls: DelegatedAgentSessionOptions[] = [];
const hostedSession = new HostedSession({ id: "delegate-adversary", cwd: getCwd() });
/** @type {Array<{ readers: number, writer: boolean }>} */
const leaseStates = [];
const leaseStates: DelegatedLeaseState[] = [];
const tool = createDelegateAgentTool({
hostedSession,
cwd: getCwd(),
Expand Down Expand Up @@ -366,8 +354,7 @@ test("delegate_agent limited to write mode refuses a role that reduces it to rea
});

test("delegate_agent propagates parent model and thinking state", async () => {
/** @type {Array<import('../delegate-agent.ts').DelegatedAgentSessionOptions>} */
const calls = [];
const calls: DelegatedAgentSessionOptions[] = [];
const hostedSession = new HostedSession({ id: "delegate-parent-state", cwd: getCwd() });
hostedSession.pushAgentInfo("Engineer", "anthropic/claude-sonnet-4", "anthropic", "engineer");
hostedSession.setThinkingLevel("high");
Expand All @@ -394,8 +381,7 @@ test("delegate_agent preserves failed writer changes and releases lease", async
try {
await fsWriteFile(join(executionCwd, "src", "pre-existing.js"), "modified\n");
const hostedSession = new HostedSession({ id: "delegate-write-fail", cwd: getCwd() });
/** @type {Array<import('../delegate-agent.ts').DelegatedAgentSessionOptions>} */
const calls = [];
const calls: DelegatedAgentSessionOptions[] = [];
const tool = createDelegateAgentTool({
hostedSession,
cwd: executionCwd,
Expand Down Expand Up @@ -473,8 +459,7 @@ test("delegate_agent returns structured failure when lease acquisition is reject
test("delegate_agent forwards cancellation and releases its lease", async () => {
const hostedSession = new HostedSession({ id: "delegate-cancel", cwd: getCwd() });
const controller = new AbortController();
/** @type {AbortSignal | undefined} */
let childSignal;
let childSignal: AbortSignal | undefined;
const tool = createDelegateAgentTool({
hostedSession,
cwd: getCwd(),
Expand Down
2 changes: 1 addition & 1 deletion src/tools/delegate-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ interface PorcelainStatusEntry {
path: string;
}

interface DelegateAgentDetails {
export interface DelegateAgentDetails {
ok: boolean;
mode: DelegationMode;
role: string;
Expand Down
Loading