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
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -1093,7 +1093,7 @@ The highest-value cross-boundary paths for later confidence analysis are visible
| Interaction contract | `src/shared/session/session-runtime-interactions.js` |
| Pi session construction and prompt bridge | `src/shared/session/session.js` |
| Agent definition layering | `src/shared/session/agents.js` |
| Agent switching | `src/shared/session/agent-switching.js` |
| Agent switching | `src/shared/session/agent-switching.ts` |
| Workflow-aware turn handling | `src/shared/session/agent-handler.js` |
| Routing orchestration | `src/shared/workflow/orchestrator.js` |
| Workflow decisions and outcome parsing | `src/shared/workflow/decisions.ts`, `src/shared/workflow/workflow-results.ts` |
Expand Down
4 changes: 2 additions & 2 deletions docs/plans/isolate-planning-and-reuse-worktrees.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ workKind: "BUG_FIX"
complexity: "HIGH"
affectedPaths:
- "src/shared/workflow/planning-worktree.ts"
- "src/shared/session/agent-switching.js"
- "src/shared/session/agent-switching.ts"
- "src/shared/session/session-runtime.js"
- "src/shared/worktree-registry.js"
- "src/tools/plan-written.ts"
Expand Down Expand Up @@ -178,7 +178,7 @@ shifts, migration or compatibility risk grows, or the Verification Plan no longe

- `src/shared/workflow/planning-worktree.ts`, `src/shared/worktree.js`, `src/shared/worktree-registry.js` — unnamed
Session ownership, binding, retained container/child worktrees, and safe target refresh.
- `src/shared/session/agent-switching.js`, `session.js`, `session-runtime.js`, `hosted-session.js`, Session persistence
- `src/shared/session/agent-switching.ts`, `session.js`, `session-runtime.js`, `hosted-session.js`, Session persistence
helpers — restore working-directory evidence before constructing tools while preserving transcript continuity.
- `src/tools/plan-written.ts`, `src/shared/workflow/sequence-review.ts` — real identity binding before review and saved
container/child document references; all tools use the selected cwd.
Expand Down
2 changes: 1 addition & 1 deletion docs/prd/session-working-directory-prd.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ under the repository's PRD policy. No implementing Plan exists yet.
context; approved same-Project scope and the four protected phases, including Plan and code review.
- [Current Git prompt context](../../src/shared/git.ts): `readGitPromptState` and `formatGitPromptState` report branch and
clean/dirty state, not Plan/Epic ownership.
- [Existing Agent switching](../../src/shared/session/agent-switching.js): `switchActiveAgent` can rebuild for another
- [Existing Agent switching](../../src/shared/session/agent-switching.ts): `switchActiveAgent` can rebuild for another
cwd and rebind the Session. This is internal support, not proof of a complete Agent-facing movement capability.
- [Epic child discovery](../../src/shared/workflow/planning-worktree.ts): `readTargetBranchPlansByParent` can read the
committed target branch instead of uncommitted files in a separate Epic worktree. The reported incident was not
Expand Down
1 change: 0 additions & 1 deletion scripts/language-policy-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
"src/shared/owner-coordination/sessions.js",
"src/shared/remote/runtime.js",
"src/shared/session/agent-handler.js",
"src/shared/session/agent-switching.js",
"src/shared/session/agents.js",
"src/shared/session/hosted-session.js",
"src/shared/session/root-session.js",
Expand Down
2 changes: 1 addition & 1 deletion src/shared/session/agent-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
type WorkflowValidationResult,
} from "../workflow/validation.ts";
import { runWorkflowValidationToStableBoundary } from "../workflow/validation.ts";
import { switchActiveAgent } from "./agent-switching.js";
import { switchActiveAgent } from "./agent-switching.ts";
import { getAgentDisplayName } from "./agents.js";
import { emitHostedSessionRuntimeEvent, emitSystemStatus, RuntimeEventTypes } from "./session-runtime-events.js";
import { requestHostedSessionInteraction, RuntimeInteractionTypes } from "./session-runtime-interactions.js";
Expand Down
2 changes: 1 addition & 1 deletion src/shared/session/agent-switching.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { assertEquals, assertRejects } from "../../testing/assertions.ts";
import { SessionManager } from "@earendil-works/pi-coding-agent";
import { fauxAssistantMessage, fauxText, getCurrentTools, getSystemMessageText } from "@earendil-works/pi-ai";
import { withRuntimeCommandFixture } from "../../cmd/testing/runtime-command-fixture.ts";
import { runActiveAgentTurn, switchActiveAgent } from "./agent-switching.js";
import { runActiveAgentTurn, switchActiveAgent } from "./agent-switching.ts";
import { __getRootSessionMetadataForTests } from "./session.js";
import { HostedSession } from "./hosted-session.js";
import { RuntimeEventTypes } from "./session-runtime-events.js";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,41 +16,84 @@ import {
shouldReuseExistingRootSession,
} from "./session.js";
import { emitHostedSessionRuntimeEvent, RuntimeEventTypes } from "./session-runtime-events.js";
/** @type {WeakMap<import('./hosted-session.js').HostedSession, { agentName: string, model?: string, cwd?: string }>} */
const switchMetadata = new WeakMap();

/** @type {WeakMap<Function, { agentName: string }>} */
const handlerMetadata = new WeakMap();
import type { AgentMessage } from "@earendil-works/pi-agent-core";
import type { SessionManager, ToolDefinition } from "@earendil-works/pi-coding-agent";
import type { HostedSession, ThinkingLevel } from "./hosted-session.js";
import type { ImageAttachment } from "./types.js";
import type { TriageMeta } from "../../tools/plan-written.ts";
import type { McpIntegration } from "../mcp/integration.ts";
import type { McpServerDefinition } from "../mcp/config.ts";
import type { ManagedOperationCapability } from "./managed-operation.ts";
import type { RequestDispatchKind } from "./request-dispatch.ts";

/**
* @typedef {Object} AgentSwitchOptions
* @property {string} agentName
* @property {string} [model]
* @property {import('./hosted-session.js').ThinkingLevel} [thinkingLevelOverride]
* @property {string} [cwd]
* @property {Array<{base64: string, mimeType: string}>} [images]
* @property {boolean} [forceRebuild]
* @property {boolean} [reloadMcpTools]
* @property {import('@earendil-works/pi-coding-agent').SessionManager} [sessionManager]
* @property {import('../../tools/plan-written.ts').TriageMeta} [triageMeta]
* @property {{ id: import('./subagent-definitions.ts').SubAgentDefinitionId, options?: import('./subagent-definitions.ts').LoadSubAgentDefinitionOptions }} [subAgentDefinition]
* @property {import('@earendil-works/pi-coding-agent').ToolDefinition[]} [customTools]
* @property {import('@earendil-works/pi-coding-agent').ToolDefinition[]} [mcpRootTools]
* @property {import('../mcp/integration.ts').McpIntegration} [mcpIntegration]
* @property {string[]} [toolNames]
* @property {string} [projectStateContext]
* @property {boolean} [includeEditFallback]
* @property {string} [debugLogPath]
* @property {boolean} [releaseActiveWorkflow]
* @property {import('./managed-operation.ts').ManagedOperationCapability} [managedOperationCapability]
* @property {import('../mcp/config.ts').McpServerDefinition[]} [mcpServers]
*/
export interface AgentSwitchOptions {
agentName: string;
model?: string;
thinkingLevelOverride?: ThinkingLevel;
cwd?: string;
images?: ImageAttachment[];
forceRebuild?: boolean;
reloadMcpTools?: boolean;
sessionManager?: SessionManager;
triageMeta?: TriageMeta;
subAgentDefinition?: Parameters<typeof ensureRootAgentSession>[0]["subAgentDefinition"];
customTools?: ToolDefinition[];
mcpRootTools?: ToolDefinition[];
mcpIntegration?: McpIntegration;
toolNames?: string[];
projectStateContext?: string;
includeEditFallback?: boolean;
debugLogPath?: string;
releaseActiveWorkflow?: boolean;
managedOperationCapability?: ManagedOperationCapability;
mcpServers?: McpServerDefinition[];
}

/**
* @param {import('./hosted-session.js').HostedSession} hostedSession
* @param {string} agentName
*/
function releaseActiveWorkflowAfterUserSwitch(hostedSession, agentName) {
export interface AgentSwitchResult {
ok: true;
agentName: string;
model?: string;
changed: boolean;
}

export interface ActiveAgentTurnOptions extends Omit<
AgentSwitchOptions,
| "thinkingLevelOverride"
| "reloadMcpTools"
| "mcpRootTools"
| "mcpIntegration"
| "releaseActiveWorkflow"
| "managedOperationCapability"
| "mcpServers"
> {
hostedSession: HostedSession;
userRequest: string;
dispatchKind?: RequestDispatchKind;
/** True when continuing the preceding failed or interrupted request. */
resumePreviousRequest?: boolean;
/** Stable identity for the controller-owned task being continued. */
taskId?: string;
signal?: AbortSignal;
}

interface AgentSwitchMetadata {
agentName: string;
model?: string;
cwd?: string;
}

interface AgentHandlerMetadata {
agentName: string;
}

type ActiveAgentHandler = NonNullable<ReturnType<HostedSession["getActiveOnMessage"]>>;

const switchMetadata = new WeakMap<HostedSession, AgentSwitchMetadata>();

const handlerMetadata = new WeakMap<ActiveAgentHandler, AgentHandlerMetadata>();

function releaseActiveWorkflowAfterUserSwitch(hostedSession: HostedSession, agentName: string) {
const workflow = hostedSession.getActiveExecutionWorkflow?.() || null;
if (!workflow) return;
const planName = typeof workflow.planName === "string" && workflow.planName ? workflow.planName : "quick-fix";
Expand All @@ -70,9 +113,8 @@ function releaseActiveWorkflowAfterUserSwitch(hostedSession, agentName) {
/**
* Release the selected Agent for RunWield-owned work without disposing its context.
*
* @param {import('./hosted-session.js').HostedSession} hostedSession
*/
export function releaseActiveAgent(hostedSession) {
export function releaseActiveAgent(hostedSession: HostedSession) {
hostedSession.assertActive();
hostedSession.setRootAgentName(null);
while (hostedSession.getActiveAgentInfo()) hostedSession.popAgentInfo();
Expand All @@ -88,11 +130,11 @@ export function releaseActiveAgent(hostedSession) {
* The target root Agent Session is built before the active handler is replaced,
* so construction failures leave the previous root/handler pair intact.
*
* @param {import('./hosted-session.js').HostedSession} hostedSession
* @param {AgentSwitchOptions} options
* @returns {Promise<{ ok: true, agentName: string, model?: string, changed: boolean }>}
*/
export async function switchActiveAgent(hostedSession, options) {
export async function switchActiveAgent(
hostedSession: HostedSession,
options: AgentSwitchOptions,
): Promise<AgentSwitchResult> {
if (!hostedSession) throw new Error("switchActiveAgent requires a HostedSession");
hostedSession.assertActive();
const managedOperationCapability =
Expand Down Expand Up @@ -284,37 +326,12 @@ export async function switchActiveAgent(hostedSession, options) {
return { ok: true, agentName, model: options.model, changed };
}

/**
* @typedef {Object} ActiveAgentTurnOptions
* @property {import('./hosted-session.js').HostedSession} hostedSession
* @property {string} agentName
* @property {string} userRequest
* @property {Array<{base64: string, mimeType: string}>} [images]
* @property {import('@earendil-works/pi-coding-agent').SessionManager} [sessionManager]
* @property {import('../../tools/plan-written.ts').TriageMeta} [triageMeta]
* @property {string} [model]
* @property {string} [cwd]
* @property {boolean} [forceRebuild]
* @property {{ id: import('./subagent-definitions.ts').SubAgentDefinitionId, options?: import('./subagent-definitions.ts').LoadSubAgentDefinitionOptions }} [subAgentDefinition]
* @property {import('@earendil-works/pi-coding-agent').ToolDefinition[]} [customTools]
* @property {string[]} [toolNames]
* @property {string} [projectStateContext]
* @property {boolean} [includeEditFallback]
* @property {string} [debugLogPath]
* @property {import('./request-dispatch.ts').RequestDispatchKind} [dispatchKind]
* @property {boolean} [resumePreviousRequest] True when continuing the preceding failed or interrupted request.
* @property {string} [taskId] Stable identity for the controller-owned task being continued.
* @property {AbortSignal} [signal]
*/

/**
* Activate an Agent and run its root turn without exposing a state where the
* root session and interactive handler belong to different Agents.
*
* @param {ActiveAgentTurnOptions} options
* @returns {Promise<import('@earendil-works/pi-agent-core').AgentMessage[]>}
*/
export async function runActiveAgentTurn(options) {
export async function runActiveAgentTurn(options: ActiveAgentTurnOptions): Promise<AgentMessage[]> {
const {
hostedSession,
agentName,
Expand Down
2 changes: 1 addition & 1 deletion src/shared/session/agy-cli-execution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import { installAgyCliMcpSetup } from "./backends/agy-cli/mcp-setup.ts";
import { startMcpIntegration } from "../mcp/integration.ts";
import { getRootSessionBranchEntries } from "./root-session.js";
import { createReplayEvents } from "./session-transcript-projection.js";
import { runActiveAgentTurn } from "./agent-switching.js";
import { runActiveAgentTurn } from "./agent-switching.ts";
import { listPendingWorkflowToolEvents } from "../workflow/workflow-tool-events.ts";
import { createValidationSessionPort } from "../workflow/validation-session-adapter.ts";
import {
Expand Down
4 changes: 2 additions & 2 deletions src/shared/session/architecture-boundary.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,15 +357,15 @@ test("active and isolated Agents have exactly one production lifecycle boundary
const activeMutationModules = new Set([
"src/shared/session/hosted-session.js",
"src/shared/session/session.js",
"src/shared/session/agent-switching.js",
"src/shared/session/agent-switching.ts",
// Test-only workflow driver exercises the real validation machinery.
"src/testing/usage-export-journey-workflows.ts",
]);
const activeTurnModules = new Set([
"src/shared/session/session.js",
"src/shared/session/agent-handler.js",
"src/shared/session/agent-handler.ts",
"src/shared/session/agent-switching.js",
"src/shared/session/agent-switching.ts",
"src/shared/session/root-workflow-turn.ts",
"src/shared/workflow/orchestrator.ts",
]);
Expand Down
4 changes: 2 additions & 2 deletions src/shared/session/runtime/agent-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AGENTS } from "../../../constants.js";
import { recordManualModelSelection } from ".././active-agent-session.ts";
import { resolveActiveWorkflowRuntimeAgent } from "../../workflow/execution-agent.ts";
import { getAgentBusyLines, getAgentDisplayName } from ".././agents.js";
import { switchActiveAgent } from ".././agent-switching.js";
import { switchActiveAgent } from ".././agent-switching.ts";
import {
drainSessionCompactionMetrics,
getConfiguredAgentModel,
Expand Down Expand Up @@ -587,7 +587,7 @@ export class RuntimeAgentSettings {

async activateSessionAgent(
hostedSession: import(".././hosted-session.js").HostedSession,
options: import(".././agent-switching.js").AgentSwitchOptions,
options: import(".././agent-switching.ts").AgentSwitchOptions,
) {
let pendingCreation = this.managedOperations.getPendingCreationProof(hostedSession.id);
if (!pendingCreation && this.lifecycle.hasPendingProject(hostedSession.id)) {
Expand Down
2 changes: 1 addition & 1 deletion src/shared/session/runtime/workflows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { retrySessionWorkRecord } from "./work-record-retry.ts";
import { buildPlanConversationRequest } from "../../workflow/plan-review-conversation.ts";
import { AGENTS, SUBAGENTS } from "../../../constants.js";
import { resolvePlanExecutionRuntimeAgent } from "../../workflow/execution-agent.ts";
import { runActiveAgentTurn } from ".././agent-switching.js";
import { runActiveAgentTurn } from ".././agent-switching.ts";
import { emitSystemStatus, RuntimeEventTypes } from ".././session-runtime-events.js";
import { readPersistedPendingSegmentContinuationEntry } from ".././workflow-context-session.js";
import { prepareSessionPlanningWorktree } from "../../workflow/session-planning-worktree.ts";
Expand Down
2 changes: 1 addition & 1 deletion src/shared/session/session-runtime.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { withRuntimeCommandFixture } from "../../cmd/testing/runtime-command-fix
import { __resetSettingsForTests, setCustomSetting } from "../settings.js";
import { SessionHost } from "./session-host.ts";
import { captureTranscriptEvidence } from "./session-transcript-projection.js";
import { switchActiveAgent } from "./agent-switching.js";
import { switchActiveAgent } from "./agent-switching.ts";
import { RuntimeEventTypes } from "./session-runtime-events.js";
import { RuntimeInteractionTypes } from "./session-runtime-interactions.js";
import { createSessionRuntime, SessionRuntime, SessionTurnInProgressError } from "./session-runtime.ts";
Expand Down
2 changes: 1 addition & 1 deletion src/shared/session/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -4313,7 +4313,7 @@ export function getRootSessionSwitchState(hostedSession) {

/**
* @param {import('./hosted-session.js').HostedSession} hostedSession
* @returns {Partial<import('./agent-switching.js').AgentSwitchOptions>}
* @returns {Partial<import('./agent-switching.ts').AgentSwitchOptions>}
*/
export function getRootSessionRebuildOptions(hostedSession) {
const session = /** @type {any} */ (hostedSession?.getRootAgentSession?.());
Expand Down
2 changes: 1 addition & 1 deletion src/shared/workflow/engineer-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AGENTS } from "../../constants.js";
import { getAgentDisplayName } from "../session/agents.js";
import { emitSystemStatus } from "../session/session-runtime-events.js";
import { AgyCliBackendError } from "../session/backends/agy-cli/failure.ts";
import { runActiveAgentTurn } from "../session/agent-switching.js";
import { runActiveAgentTurn } from "../session/agent-switching.ts";
import { createPairCheckpointTool } from "../../tools/pair-checkpoint.ts";
import { buildEngineerRequest } from "./workflow-prompts.ts";
import { acknowledgeTaskCompletion, claimPendingTaskCompletion } from "../session/task-completion-session.ts";
Expand Down
2 changes: 1 addition & 1 deletion src/shared/workflow/orchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { ensurePlansDir, loadPlan } from "../../plan-store.js";
import { probeGitRepository } from "../git.ts";
import { hasNonGitExecutionConsent, rememberNonGitExecutionConsent } from "../non-git-execution-consent.ts";
import { resolvePlanExecutionRuntimeAgent } from "./execution-agent.ts";
import { switchActiveAgent } from "../session/agent-switching.js";
import { switchActiveAgent } from "../session/agent-switching.ts";
import { runRootTurn } from "../session/session.js";
import { getAgentDisplayName } from "../session/agents.js";
import { sanitizeSessionName } from "../session-name.ts";
Expand Down
2 changes: 1 addition & 1 deletion src/shared/workflow/planning-agent.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @ts-nocheck: extracted from checked JSDoc workflow.js; tightening types is out of scope for this structural split.
import { switchActiveAgent } from "../session/agent-switching.js";
import { switchActiveAgent } from "../session/agent-switching.ts";
import { runRootTurnUntilRootWorkflowEvent } from "../session/root-workflow-turn.ts";
import { claimWorkflowToolEvent, settleWorkflowToolEvent } from "./workflow-tool-events.ts";
import type { SessionManager } from "@earendil-works/pi-coding-agent";
Expand Down
2 changes: 1 addition & 1 deletion src/shared/workflow/validation-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { settleWorkflowToolEvent } from "./workflow-tool-events.ts";
import { createManualQaCompletedTool } from "../../tools/manual-qa-completed.ts";
import { logValidationFailure } from "./validation-state-errors.ts";
import { SessionManager } from "@earendil-works/pi-coding-agent";
import { runActiveAgentTurn, switchActiveAgent } from "../session/agent-switching.js";
import { runActiveAgentTurn, switchActiveAgent } from "../session/agent-switching.ts";
import {
requestHostedSessionInteraction,
RuntimeInteractionOutcomes,
Expand Down
2 changes: 1 addition & 1 deletion src/shared/workflow/validation-session-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { type ReviewInspection } from "./review-inspection.ts";
import { createReviewCompletedTool } from "../../tools/review-complete.ts";
import { createQaChecklistGeneratedTool } from "../../tools/qa-checklist-generated.ts";
import { settleWorkflowToolEvent } from "./workflow-tool-events.ts";
import { releaseActiveAgent, switchActiveAgent } from "../session/agent-switching.js";
import { releaseActiveAgent, switchActiveAgent } from "../session/agent-switching.ts";
import {
type AgentTurnOutcome,
type IndependentRepairTurnRequest,
Expand Down
Loading
Loading