Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
689 lines
20 KiB
TypeScript
689 lines
20 KiB
TypeScript
import {
|
|
normalizeAgentActivitySession,
|
|
selectEngineHasVisibleQueuedSubmit,
|
|
selectPendingSubmitsForSession,
|
|
type AgentActivityInteraction,
|
|
type AgentActivityTurn
|
|
} from "@tutti-os/agent-activity-core";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import {
|
|
createTestAgentSessionEngine,
|
|
createTestAgentSessionEngineWithEffects
|
|
} from "../../../shared/testing/createTestAgentSessionEngine";
|
|
import {
|
|
agentComposerDraftPrompt,
|
|
buildAgentComposerDraft,
|
|
emptyAgentComposerDraft
|
|
} from "../model/agentComposerDraft";
|
|
import type {
|
|
AgentComposerDraft,
|
|
SubmittedDraftSnapshot
|
|
} from "../model/agentGuiNodeTypes";
|
|
import {
|
|
agentGUIInteractionDraftSettlementKey,
|
|
AgentGUIEngineSettlementController
|
|
} from "./AgentGUIEngineSettlementController";
|
|
|
|
describe("AgentGUIEngineSettlementController", () => {
|
|
it("clears a matching home draft after activation confirmation", () => {
|
|
const engine = createTestAgentSessionEngine();
|
|
const sourceScopeKey = "project:/workspace/app";
|
|
const submittedDraft: AgentComposerDraft = [
|
|
{ type: "text", text: "first" }
|
|
];
|
|
const snapshots: Record<string, SubmittedDraftSnapshot> = {
|
|
"submit-1": { content: submittedDraft, sourceScopeKey }
|
|
};
|
|
let drafts: Record<string, AgentComposerDraft> = {
|
|
[sourceScopeKey]: submittedDraft
|
|
};
|
|
const controller = new AgentGUIEngineSettlementController({
|
|
applyDraftUpdate: (update) => {
|
|
drafts = update(drafts);
|
|
},
|
|
engine,
|
|
snapshots
|
|
});
|
|
const detach = controller.attach();
|
|
|
|
requestActivation(engine, "submit-1");
|
|
engine.dispatch({
|
|
type: "session/upserted",
|
|
session: normalizeAgentActivitySession({
|
|
activeTurnId: null,
|
|
agentSessionId: "session-1",
|
|
createdAtUnixMs: Date.now() + 60_000,
|
|
cwd: "/workspace/app",
|
|
latestTurnInteractions: [],
|
|
pendingInteractions: [],
|
|
provider: "codex",
|
|
title: "first",
|
|
workspaceId: "test-workspace"
|
|
})
|
|
});
|
|
|
|
expect(agentComposerDraftPrompt(drafts[sourceScopeKey]!)).toBe("");
|
|
expect(drafts["session:session-1"]).toEqual(emptyAgentComposerDraft());
|
|
expect(snapshots).toEqual({});
|
|
detach();
|
|
engine.dispose();
|
|
});
|
|
|
|
it("seeds the new session draft with submitted connectors after home activation", () => {
|
|
const engine = createTestAgentSessionEngine();
|
|
const sourceScopeKey = "home";
|
|
const submittedDraft = buildAgentComposerDraft({
|
|
prompt: "first",
|
|
connectors: [{ connectorKey: "lark-cli" }, { connectorKey: "github" }]
|
|
});
|
|
const snapshots: Record<string, SubmittedDraftSnapshot> = {
|
|
"submit-1": { content: submittedDraft, sourceScopeKey }
|
|
};
|
|
let drafts: Record<string, AgentComposerDraft> = {
|
|
[sourceScopeKey]: submittedDraft
|
|
};
|
|
const controller = new AgentGUIEngineSettlementController({
|
|
applyDraftUpdate: (update) => {
|
|
drafts = update(drafts);
|
|
},
|
|
engine,
|
|
snapshots
|
|
});
|
|
const detach = controller.attach();
|
|
|
|
requestActivation(engine, "submit-1");
|
|
engine.dispatch({
|
|
type: "session/upserted",
|
|
session: normalizeAgentActivitySession({
|
|
activeTurnId: null,
|
|
agentSessionId: "session-1",
|
|
createdAtUnixMs: Date.now() + 60_000,
|
|
cwd: "/workspace/app",
|
|
latestTurnInteractions: [],
|
|
pendingInteractions: [],
|
|
provider: "codex",
|
|
title: "first",
|
|
workspaceId: "test-workspace"
|
|
})
|
|
});
|
|
|
|
expect(drafts[sourceScopeKey]).toEqual(emptyAgentComposerDraft());
|
|
expect(drafts["session:session-1"]).toEqual(
|
|
buildAgentComposerDraft({
|
|
prompt: "",
|
|
connectors: [{ connectorKey: "lark-cli" }, { connectorKey: "github" }]
|
|
})
|
|
);
|
|
expect(snapshots).toEqual({});
|
|
detach();
|
|
engine.dispose();
|
|
});
|
|
|
|
it("restores a failed activation only while its home draft is empty", async () => {
|
|
let rejectActivation: (error: Error) => void = vi.fn();
|
|
const engine = createTestAgentSessionEngine("test-workspace", {
|
|
execute(command) {
|
|
if (command.type !== "session/activate") {
|
|
return Promise.resolve({ ok: true });
|
|
}
|
|
return new Promise((_, reject) => {
|
|
rejectActivation = reject;
|
|
});
|
|
}
|
|
});
|
|
const sourceScopeKey = "project:/workspace/app";
|
|
const snapshots: Record<string, SubmittedDraftSnapshot> = {
|
|
"submit-1": {
|
|
content: [{ type: "text", text: "first" }],
|
|
sourceScopeKey
|
|
}
|
|
};
|
|
let drafts: Record<string, AgentComposerDraft> = {
|
|
[sourceScopeKey]: emptyAgentComposerDraft()
|
|
};
|
|
const controller = new AgentGUIEngineSettlementController({
|
|
applyDraftUpdate: (update) => {
|
|
drafts = update(drafts);
|
|
},
|
|
engine,
|
|
snapshots
|
|
});
|
|
const detach = controller.attach();
|
|
|
|
requestActivation(engine, "submit-1");
|
|
rejectActivation(new Error("activation failed"));
|
|
await vi.waitFor(() => {
|
|
expect(agentComposerDraftPrompt(drafts[sourceScopeKey]!)).toBe("first");
|
|
});
|
|
expect(snapshots).toEqual({});
|
|
detach();
|
|
engine.dispose();
|
|
});
|
|
|
|
it("restores the original image preview after a non-visible send fails", async () => {
|
|
const failed = vi.fn();
|
|
const engine = createTestAgentSessionEngine("test-workspace", {
|
|
execute(command) {
|
|
return command.type === "queue/sendPrompt"
|
|
? Promise.reject(
|
|
Object.assign(new Error("send failed"), {
|
|
code: "workspace_operation_failed",
|
|
reason: "agent.process_cleanup_pending"
|
|
})
|
|
)
|
|
: Promise.resolve({ ok: true });
|
|
}
|
|
});
|
|
engine.dispatch({
|
|
type: "session/upserted",
|
|
session: normalizeAgentActivitySession({
|
|
activeTurnId: null,
|
|
agentSessionId: "session-1",
|
|
createdAtUnixMs: Date.now(),
|
|
cwd: "/workspace/app",
|
|
latestTurnInteractions: [],
|
|
pendingInteractions: [],
|
|
provider: "codex",
|
|
title: "session",
|
|
workspaceId: "test-workspace"
|
|
})
|
|
});
|
|
const sourceScopeKey = "session:session-1";
|
|
const submittedDraft: AgentComposerDraft = [
|
|
{ type: "text", text: "" },
|
|
{
|
|
type: "image",
|
|
id: "draft-image-1",
|
|
mimeType: "image/png",
|
|
name: "screen.png",
|
|
path: "/workspace/screen.png",
|
|
previewUrl: "data:image/png;base64,aWFnZQ=="
|
|
}
|
|
];
|
|
const snapshots: Record<string, SubmittedDraftSnapshot> = {
|
|
"submit-1": {
|
|
content: submittedDraft,
|
|
sourceScopeKey
|
|
}
|
|
};
|
|
let drafts: Record<string, AgentComposerDraft> = {
|
|
[sourceScopeKey]: emptyAgentComposerDraft()
|
|
};
|
|
const controller = new AgentGUIEngineSettlementController({
|
|
applyDraftUpdate: (update) => {
|
|
drafts = update(drafts);
|
|
},
|
|
engine,
|
|
isCurrentConversation: (agentSessionId) => agentSessionId === "session-1",
|
|
onSubmitFailed: failed,
|
|
snapshots
|
|
});
|
|
const detach = controller.attach();
|
|
|
|
engine.dispatch({
|
|
type: "submit/requested",
|
|
agentSessionId: "session-1",
|
|
clientSubmitId: "submit-1",
|
|
content: [
|
|
{
|
|
type: "image",
|
|
mimeType: "image/png",
|
|
name: "screen.png",
|
|
path: "/workspace/screen.png"
|
|
}
|
|
],
|
|
expiresAtUnixMs: Date.now() + 60_000,
|
|
requestedAtUnixMs: Date.now(),
|
|
workspaceId: "test-workspace"
|
|
});
|
|
|
|
await vi.waitFor(() => {
|
|
expect(drafts[sourceScopeKey]).toEqual(submittedDraft);
|
|
});
|
|
expect(snapshots).toEqual({});
|
|
expect(failed).toHaveBeenCalledTimes(1);
|
|
expect(failed.mock.calls[0]?.[0]).toMatchObject({
|
|
agentSessionId: "session-1",
|
|
errorCode: "workspace_operation_failed",
|
|
errorMessage: "send failed",
|
|
errorReason: "agent.process_cleanup_pending",
|
|
status: "failed"
|
|
});
|
|
detach();
|
|
engine.dispose();
|
|
});
|
|
|
|
it("does not duplicate a failed visible queued submit into the composer", async () => {
|
|
const engine = createTestAgentSessionEngine("test-workspace", {
|
|
execute(command) {
|
|
return command.type === "queue/sendPrompt"
|
|
? Promise.reject(new Error("send failed"))
|
|
: Promise.resolve({ ok: true });
|
|
}
|
|
});
|
|
engine.dispatch({
|
|
type: "session/upserted",
|
|
session: normalizeAgentActivitySession({
|
|
activeTurnId: "turn-1",
|
|
agentSessionId: "session-1",
|
|
createdAtUnixMs: Date.now(),
|
|
cwd: "/workspace/app",
|
|
latestTurnInteractions: [],
|
|
pendingInteractions: [],
|
|
provider: "codex",
|
|
title: "session",
|
|
workspaceId: "test-workspace"
|
|
})
|
|
});
|
|
engine.dispatch({
|
|
live: true,
|
|
type: "turn/upserted",
|
|
turn: {
|
|
agentSessionId: "session-1",
|
|
origin: "user_prompt",
|
|
phase: "running",
|
|
startedAtUnixMs: Date.now(),
|
|
turnId: "turn-1",
|
|
updatedAtUnixMs: Date.now()
|
|
}
|
|
});
|
|
const sourceScopeKey = "session:session-1";
|
|
const submittedDraft: AgentComposerDraft = [
|
|
{ type: "text", text: "" },
|
|
{
|
|
type: "image",
|
|
id: "draft-image-1",
|
|
mimeType: "image/png",
|
|
name: "screen.png",
|
|
path: "/workspace/screen.png",
|
|
previewUrl: "data:image/png;base64,aWFnZQ=="
|
|
}
|
|
];
|
|
const snapshots: Record<string, SubmittedDraftSnapshot> = {
|
|
"submit-1": { content: submittedDraft, sourceScopeKey }
|
|
};
|
|
let drafts: Record<string, AgentComposerDraft> = {
|
|
[sourceScopeKey]: emptyAgentComposerDraft()
|
|
};
|
|
const controller = new AgentGUIEngineSettlementController({
|
|
applyDraftUpdate: (update) => {
|
|
drafts = update(drafts);
|
|
},
|
|
engine,
|
|
snapshots
|
|
});
|
|
const detach = controller.attach();
|
|
|
|
engine.dispatch({
|
|
type: "submit/requested",
|
|
agentSessionId: "session-1",
|
|
clientSubmitId: "submit-1",
|
|
content: [
|
|
{
|
|
type: "image",
|
|
mimeType: "image/png",
|
|
name: "screen.png",
|
|
path: "/workspace/screen.png"
|
|
}
|
|
],
|
|
expiresAtUnixMs: Date.now() + 60_000,
|
|
requestedAtUnixMs: Date.now(),
|
|
workspaceId: "test-workspace"
|
|
});
|
|
expect(
|
|
selectEngineHasVisibleQueuedSubmit(
|
|
engine.getSnapshot(),
|
|
"session-1",
|
|
"submit-1"
|
|
)
|
|
).toBe(true);
|
|
engine.dispatch({
|
|
live: true,
|
|
type: "turn/upserted",
|
|
turn: {
|
|
agentSessionId: "session-1",
|
|
origin: "user_prompt",
|
|
outcome: "completed",
|
|
phase: "settled",
|
|
startedAtUnixMs: Date.now() - 1,
|
|
turnId: "turn-1",
|
|
updatedAtUnixMs: Date.now()
|
|
}
|
|
});
|
|
|
|
await vi.waitFor(() => {
|
|
expect(
|
|
selectPendingSubmitsForSession(engine.getSnapshot(), "session-1").find(
|
|
(submit) => submit.clientSubmitId === "submit-1"
|
|
)?.status
|
|
).toBe("failed");
|
|
});
|
|
expect(
|
|
selectEngineHasVisibleQueuedSubmit(
|
|
engine.getSnapshot(),
|
|
"session-1",
|
|
"submit-1"
|
|
)
|
|
).toBe(true);
|
|
expect(drafts[sourceScopeKey]).toEqual(emptyAgentComposerDraft());
|
|
expect(snapshots["submit-1"]?.content).toEqual(submittedDraft);
|
|
detach();
|
|
engine.dispose();
|
|
});
|
|
|
|
it("restores a composer answer when the interaction response fails", async () => {
|
|
let rejectResponse: (error: Error) => void = vi.fn();
|
|
const engine = createTestAgentSessionEngine("test-workspace", {
|
|
execute(command) {
|
|
if (command.type !== "interaction/respond") {
|
|
return Promise.resolve({ ok: true });
|
|
}
|
|
return new Promise((_, reject) => {
|
|
rejectResponse = reject;
|
|
});
|
|
}
|
|
});
|
|
const { interaction, session } = waitingQuestionSession();
|
|
engine.dispatch({ sessions: [session], type: "session/snapshotReceived" });
|
|
const sourceScopeKey = "session:session-1";
|
|
const submittedDraft: AgentComposerDraft = [
|
|
{ type: "text", text: "You decide" }
|
|
];
|
|
const settlementKey = agentGUIInteractionDraftSettlementKey(interaction);
|
|
const interactionDraftSettlements = {
|
|
[settlementKey]: {
|
|
agentSessionId: interaction.agentSessionId,
|
|
requestId: interaction.requestId,
|
|
submittedDraftSnapshot: {
|
|
content: submittedDraft,
|
|
sourceScopeKey,
|
|
targetAgentSessionId: interaction.agentSessionId
|
|
},
|
|
turnId: interaction.turnId
|
|
}
|
|
};
|
|
let drafts: Record<string, AgentComposerDraft> = {
|
|
[sourceScopeKey]: emptyAgentComposerDraft()
|
|
};
|
|
const controller = new AgentGUIEngineSettlementController({
|
|
applyDraftUpdate: (update) => {
|
|
drafts = update(drafts);
|
|
},
|
|
engine,
|
|
interactionDraftSettlements,
|
|
snapshots: {}
|
|
});
|
|
const detach = controller.attach();
|
|
|
|
expect(
|
|
engine.submitInteractionResponse({
|
|
action: "submit",
|
|
agentSessionId: interaction.agentSessionId,
|
|
payload: {
|
|
answers: ["You decide"],
|
|
answersByQuestionId: { "execution-mode": "You decide" }
|
|
},
|
|
requestId: interaction.requestId,
|
|
turnId: interaction.turnId
|
|
})
|
|
).toBe(true);
|
|
rejectResponse(new Error("response failed"));
|
|
|
|
await vi.waitFor(() => {
|
|
expect(agentComposerDraftPrompt(drafts[sourceScopeKey]!)).toBe(
|
|
"You decide"
|
|
);
|
|
});
|
|
expect(interactionDraftSettlements).toEqual({});
|
|
detach();
|
|
engine.dispose();
|
|
});
|
|
|
|
it("keeps a composer answer cleared after the interaction is answered", () => {
|
|
const engine = createTestAgentSessionEngine();
|
|
const { interaction, session } = waitingQuestionSession();
|
|
engine.dispatch({ sessions: [session], type: "session/snapshotReceived" });
|
|
const sourceScopeKey = "session:session-1";
|
|
const settlementKey = agentGUIInteractionDraftSettlementKey(interaction);
|
|
const submittedDraft: AgentComposerDraft = [
|
|
{ type: "text", text: "You decide" }
|
|
];
|
|
const interactionDraftSettlements = {
|
|
[settlementKey]: {
|
|
agentSessionId: interaction.agentSessionId,
|
|
requestId: interaction.requestId,
|
|
submittedDraftSnapshot: {
|
|
content: submittedDraft,
|
|
sourceScopeKey,
|
|
targetAgentSessionId: interaction.agentSessionId
|
|
},
|
|
turnId: interaction.turnId
|
|
}
|
|
};
|
|
let drafts: Record<string, AgentComposerDraft> = {
|
|
[sourceScopeKey]: emptyAgentComposerDraft()
|
|
};
|
|
const controller = new AgentGUIEngineSettlementController({
|
|
applyDraftUpdate: (update) => {
|
|
drafts = update(drafts);
|
|
},
|
|
engine,
|
|
interactionDraftSettlements,
|
|
snapshots: {}
|
|
});
|
|
const detach = controller.attach();
|
|
|
|
engine.dispatch({
|
|
interaction: { ...interaction, status: "answered", updatedAtUnixMs: 3 },
|
|
type: "interaction/upserted"
|
|
});
|
|
|
|
expect(drafts[sourceScopeKey]).toEqual(emptyAgentComposerDraft());
|
|
expect(interactionDraftSettlements).toEqual({});
|
|
detach();
|
|
engine.dispose();
|
|
});
|
|
|
|
it("settles a Goal draft when Host durably accepts an applying operation", async () => {
|
|
const engine = createTestAgentSessionEngineWithEffects("test-workspace", {
|
|
controlGoal: () =>
|
|
Promise.resolve({
|
|
goal: { objective: "ship it", status: "active" },
|
|
operationId: "goal-operation-1",
|
|
session: sessionWithGoal("old goal"),
|
|
state: {
|
|
desired: { objective: "ship it", status: "active" },
|
|
lastEvidence: { source: "test" },
|
|
observed: { objective: "old goal", status: "active" },
|
|
pendingOperationId: "goal-operation-1",
|
|
revision: 2,
|
|
syncStatus: "applying",
|
|
tombstoned: false,
|
|
updatedAtUnixMs: 2
|
|
}
|
|
})
|
|
});
|
|
engine.dispatch({
|
|
session: sessionWithGoal("old goal"),
|
|
type: "session/upserted"
|
|
});
|
|
const sourceScopeKey = "session:session-1";
|
|
const submittedDraft = buildAgentComposerDraft({
|
|
prompt: "/goal ship it",
|
|
connectors: [{ connectorKey: "lark-cli" }]
|
|
});
|
|
let drafts: Record<string, AgentComposerDraft> = {
|
|
[sourceScopeKey]: submittedDraft
|
|
};
|
|
const goalControlSettlements = {
|
|
"session-1": {
|
|
action: "set" as const,
|
|
clientSubmitId: "goal-submit-1",
|
|
submittedDraftSnapshot: {
|
|
content: submittedDraft,
|
|
sourceScopeKey,
|
|
targetAgentSessionId: "session-1"
|
|
}
|
|
}
|
|
};
|
|
const controller = new AgentGUIEngineSettlementController({
|
|
applyDraftUpdate: (update) => {
|
|
drafts = update(drafts);
|
|
},
|
|
engine,
|
|
goalControlSettlements,
|
|
snapshots: {}
|
|
});
|
|
const detach = controller.attach();
|
|
|
|
expect(
|
|
engine.controlGoal({
|
|
action: "set",
|
|
agentSessionId: "session-1",
|
|
clientSubmitId: "goal-submit-1",
|
|
objective: "ship it"
|
|
}).accepted
|
|
).toBe(true);
|
|
|
|
await vi.waitFor(() => {
|
|
expect(drafts[sourceScopeKey]).toEqual(
|
|
buildAgentComposerDraft({
|
|
prompt: "",
|
|
connectors: [{ connectorKey: "lark-cli" }]
|
|
})
|
|
);
|
|
});
|
|
expect(goalControlSettlements).toEqual({});
|
|
detach();
|
|
engine.dispose();
|
|
});
|
|
|
|
it("reports a definitive Goal rejection without clearing its draft", async () => {
|
|
const rejection = Object.assign(new Error("invalid goal"), {
|
|
code: "invalid_request"
|
|
});
|
|
const engine = createTestAgentSessionEngineWithEffects("test-workspace", {
|
|
controlGoal: () => Promise.reject(rejection)
|
|
});
|
|
engine.dispatch({
|
|
session: sessionWithGoal("old goal"),
|
|
type: "session/upserted"
|
|
});
|
|
const failed = vi.fn();
|
|
const goalControlSettlements = {
|
|
"session-1": {
|
|
action: "clear" as const,
|
|
clientSubmitId: "goal-submit-1",
|
|
submittedDraftSnapshot: null
|
|
}
|
|
};
|
|
const controller = new AgentGUIEngineSettlementController({
|
|
applyDraftUpdate: vi.fn(),
|
|
engine,
|
|
goalControlSettlements,
|
|
isCurrentConversation: () => true,
|
|
onGoalControlFailed: failed,
|
|
snapshots: {}
|
|
});
|
|
const detach = controller.attach();
|
|
|
|
engine.controlGoal({
|
|
action: "clear",
|
|
agentSessionId: "session-1",
|
|
clientSubmitId: "goal-submit-1"
|
|
});
|
|
|
|
await vi.waitFor(() => expect(failed).toHaveBeenCalledTimes(1));
|
|
expect(failed.mock.calls[0]?.[0]).toMatchObject({
|
|
clientSubmitId: "goal-submit-1",
|
|
errorCode: "invalid_request",
|
|
status: "failed"
|
|
});
|
|
expect(goalControlSettlements).toEqual({});
|
|
detach();
|
|
engine.dispose();
|
|
});
|
|
});
|
|
|
|
function sessionWithGoal(objective: string) {
|
|
return normalizeAgentActivitySession({
|
|
activeTurnId: null,
|
|
agentSessionId: "session-1",
|
|
cwd: "/workspace/app",
|
|
goal: { objective, status: "active" },
|
|
latestTurnInteractions: [],
|
|
pendingInteractions: [],
|
|
provider: "codex",
|
|
title: "session",
|
|
workspaceId: "test-workspace"
|
|
});
|
|
}
|
|
|
|
function waitingQuestionSession(): {
|
|
interaction: AgentActivityInteraction;
|
|
session: ReturnType<typeof normalizeAgentActivitySession>;
|
|
} {
|
|
const turn: AgentActivityTurn = {
|
|
agentSessionId: "session-1",
|
|
origin: "user_prompt",
|
|
phase: "waiting",
|
|
startedAtUnixMs: 1,
|
|
turnId: "turn-1",
|
|
updatedAtUnixMs: 2
|
|
};
|
|
const interaction: AgentActivityInteraction = {
|
|
agentSessionId: "session-1",
|
|
createdAtUnixMs: 2,
|
|
input: {
|
|
questions: [
|
|
{
|
|
allowFreeText: true,
|
|
header: "Execution",
|
|
id: "execution-mode",
|
|
options: [{ description: "Check first", label: "Safe" }],
|
|
question: "How should the task continue?"
|
|
}
|
|
]
|
|
},
|
|
kind: "question",
|
|
requestId: "request-1",
|
|
status: "pending",
|
|
turnId: turn.turnId,
|
|
updatedAtUnixMs: 2
|
|
};
|
|
return {
|
|
interaction,
|
|
session: normalizeAgentActivitySession({
|
|
activeTurn: turn,
|
|
activeTurnId: turn.turnId,
|
|
agentSessionId: "session-1",
|
|
cwd: "/workspace/app",
|
|
latestTurn: turn,
|
|
latestTurnInteractions: [interaction],
|
|
pendingInteractions: [interaction],
|
|
provider: "codex",
|
|
title: "session",
|
|
workspaceId: "test-workspace"
|
|
})
|
|
};
|
|
}
|
|
|
|
function requestActivation(
|
|
engine: ReturnType<typeof createTestAgentSessionEngine>,
|
|
clientSubmitId: string
|
|
): void {
|
|
engine.dispatch({
|
|
type: "activation/requested",
|
|
agentSessionId: "session-1",
|
|
agentTargetId: "local:codex",
|
|
clientSubmitId,
|
|
content: [{ type: "text", text: "first" }],
|
|
cwd: "/workspace/app",
|
|
expiresAtUnixMs: Date.now() + 45_000,
|
|
mode: "new",
|
|
requestedAtUnixMs: Date.now(),
|
|
requestId: "request-1",
|
|
workspaceId: "test-workspace"
|
|
});
|
|
}
|