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
16 changes: 0 additions & 16 deletions packages/features/src/coding-agent-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -540,22 +540,6 @@ export const dictationTranscribeAnswerViewSchema = EffectSchema.Union([

export type DictationTranscribeAnswerView = typeof dictationTranscribeAnswerViewSchema.Type;

/** One task's inbox past a cursor, as the window asks it: the task, and where the window stands. */
export const codingAgentInboxParamsSchema = EffectSchema.Struct({
taskId: EffectSchema.NonEmptyString,
after: EffectSchema.NonEmptyString,
});

export type CodingAgentInboxParams = typeof codingAgentInboxParamsSchema.Type;

/** One agent's inbox past a cursor, as the window asks it: the agent, and where the window stands; it is heard as a task's inbox is. */
export const codingAgentAgentInboxParamsSchema = EffectSchema.Struct({
agentId: EffectSchema.NonEmptyString,
after: EffectSchema.NonEmptyString,
});

export type CodingAgentAgentInboxParams = typeof codingAgentAgentInboxParamsSchema.Type;

/** Whose inbox is followed: a plan's, or one agent's. */
export const INBOX_SCOPE_KIND = {
PLAN: "plan",
Expand Down
3 changes: 0 additions & 3 deletions packages/gateway/src/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,9 +287,6 @@ export const GATEWAY_METHOD = {
PLAN_TEMPLATE_RESTORE: "planTemplate.restore",
/** One clip dictated into an agent's message box, carried once to the service; its words, or why there are none. Nothing of it is kept. */
DICTATION_TRANSCRIBE: "dictation.transcribe",
/** One task's inbox past a cursor: every item its coding agents posted for the developer, held open by the service while nothing changed. */
CODING_AGENTS_INBOX: "codingAgents.inbox",
CODING_AGENTS_AGENT_INBOX: "codingAgents.agentInbox",
/**
* One plan's inbox, or one agent's, followed from now until it is let go
* or the account signs out, over Neon Realtime. What it holds is heard as
Expand Down
47 changes: 16 additions & 31 deletions packages/host/src/act-port/agent-inbox-procedures.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,6 @@ const UNANSWERED = { failure: CODING_AGENT_CALL_FAILURE.UNANSWERED } as const;

/** Each kind's refusal, as the window drew it before the kinds moved onto the port. */
const REFUSAL = {
[AGENT_INBOX_ACT_KIND.CODING_AGENTS_INBOX]: "Could not read the task's inbox on this system.",
[AGENT_INBOX_ACT_KIND.CODING_AGENTS_AGENT_INBOX]:
"Could not read the agent's inbox on this system.",
[AGENT_INBOX_ACT_KIND.CODING_AGENTS_INBOX_ANSWER]:
"Could not answer the inbox item on this system.",
[AGENT_INBOX_ACT_KIND.CODING_AGENTS_INBOX_CLOSE]:
Expand Down Expand Up @@ -114,16 +111,6 @@ const ACTS: readonly {
payload: WireBoundaryInput;
ask: keyof HostAsk;
}[] = [
{
kind: AGENT_INBOX_ACT_KIND.CODING_AGENTS_INBOX,
payload: { taskId: TASK_ID, after: "0" },
ask: "codingAgentInbox",
},
{
kind: AGENT_INBOX_ACT_KIND.CODING_AGENTS_AGENT_INBOX,
payload: { agentId: AGENT_ID, after: "0" },
ask: "codingAgentAgentInbox",
},
{
kind: AGENT_INBOX_ACT_KIND.CODING_AGENTS_INBOX_ANSWER,
payload: { itemId: ITEM.id, revision: 2, reply: INBOX_REPLY.APPROVED, clientKey: "answer-1" },
Expand Down Expand Up @@ -242,10 +229,9 @@ it("a panel's ask reaches the host once with its payload, and the host's answer
);
});

it("a panel reads a task's inbox, answers, closes, and messages on an item, and hears the item as it then stands", async () => {
it("a panel answers, closes, and messages on an item, and hears the item as it then stands", async () => {
const { call, asked } = windowPort(PANEL, {
answers: {
codingAgentInbox: () => Effect.succeed({ items: [ITEM], cursor: "7" }),
codingAgentInboxAnswer: () =>
Effect.succeed({ item: { ...ITEM, status: INBOX_STATUS.ANSWERED }, taken: true }),
codingAgentInboxClose: () =>
Expand All @@ -262,10 +248,6 @@ it("a panel reads a task's inbox, answers, closes, and messages on an item, and
const answer = { itemId: ITEM.id, revision: 2, reply: INBOX_REPLY.APPROVED, clientKey: "a-1" };
const close = { itemId: ITEM.id, revision: 2, clientKey: "close-1" };

assert.deepEqual(
await call(AGENT_INBOX_ACT_KIND.CODING_AGENTS_INBOX, { taskId: TASK_ID, after: "0" }),
{ answer: { items: [ITEM], cursor: "7" } },
);
assert.deepEqual(await call(AGENT_INBOX_ACT_KIND.CODING_AGENTS_INBOX_ANSWER, answer), {
answer: { item: { ...ITEM, status: INBOX_STATUS.ANSWERED }, taken: true },
});
Expand All @@ -276,7 +258,6 @@ it("a panel reads a task's inbox, answers, closes, and messages on an item, and
answer: { item: ITEM },
});
assert.deepEqual(asked, [
["codingAgentInbox", { taskId: TASK_ID, after: "0" }],
["codingAgentInboxAnswer", answer],
["codingAgentInboxClose", close],
["codingAgentInboxMessage", message],
Expand Down Expand Up @@ -410,17 +391,21 @@ it("an answer's key its kind's view schema does not name is left out of what the
const { call } = windowPort(PANEL, {
answers: {
codingAgentInboxReadAll: () => Effect.succeed({ marked: 3, stray: true }),
codingAgentInbox: () =>
Effect.succeed({ items: [{ ...ITEM, stray: true }], cursor: "7", stray: true }),
codingAgentInboxMessage: () =>
Effect.succeed({ item: { ...ITEM, stray: true }, stray: true }),
},
});

assert.deepEqual(await call(AGENT_INBOX_ACT_KIND.CODING_AGENTS_INBOX_READ_ALL, {}), {
answer: { marked: 3 },
});
assert.deepEqual(
await call(AGENT_INBOX_ACT_KIND.CODING_AGENTS_INBOX, { taskId: TASK_ID, after: "0" }),
{ answer: { items: [ITEM], cursor: "7" } },
await call(AGENT_INBOX_ACT_KIND.CODING_AGENTS_INBOX_MESSAGE, {
itemId: ITEM.id,
text: "Also page on-call.",
clientKey: "message-2",
}),
{ answer: { item: ITEM } },
);
});

Expand All @@ -443,10 +428,6 @@ it("a payload carrying a key its kind's schema does not name is refused in the k

it("a payload its kind's schema refuses, or none where the kind takes one, is refused in the kind's own words, and the host is not asked", async () => {
const { call, asked } = windowPort(PANEL);
assert.deepEqual(
await call(AGENT_INBOX_ACT_KIND.CODING_AGENTS_INBOX, { taskId: "", after: "0" }),
{ refused: REFUSAL[AGENT_INBOX_ACT_KIND.CODING_AGENTS_INBOX] },
);
assert.deepEqual(
await call(AGENT_INBOX_ACT_KIND.CODING_AGENTS_INBOX_READ_STATE, {
itemId: ITEM.id,
Expand All @@ -467,12 +448,16 @@ it("an item carrying a block and a response type this build does not know reache
response: { type: "signature" },
};
const { call } = windowPort(PANEL, {
answers: { codingAgentInbox: () => Effect.succeed({ items: [newer], cursor: "7" }) },
answers: { codingAgentInboxMessage: () => Effect.succeed({ item: newer }) },
});

assert.deepEqual(
await call(AGENT_INBOX_ACT_KIND.CODING_AGENTS_INBOX, { taskId: TASK_ID, after: "0" }),
{ answer: { items: [newer], cursor: "7" } },
await call(AGENT_INBOX_ACT_KIND.CODING_AGENTS_INBOX_MESSAGE, {
itemId: ITEM.id,
text: "Ship it.",
clientKey: "message-3",
}),
{ answer: { item: newer } },
);
});

Expand Down
34 changes: 7 additions & 27 deletions packages/host/src/act-port/agent-inbox-procedures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,29 @@
* The one check these procedures make is who asked, as for every task act:
* only a panel draws the Inbox and the chat, so every one refuses the
* takeover and the hidden voice window, in its own sentence. What the
* service answers comes back as the panel's own answer. The task's inbox is
* answered to the panel alone: the notices (`@luke/host/agent-notices`)
* watch what waits across the account on a read of their own. A
* workspace's chat is followed while a panel holds its stream open
* (`CODING_AGENTS_CHAT_FOLLOW`): the host follows it on the first stream of
* it and lets it go with the last, and every change it tells reaches every
* stream of it. How many streams stand open of each chat is all else held
* here.
* service answers comes back as the panel's own answer. A task's or an
* agent's inbox, and a workspace's chat, are each followed while a panel
* holds its stream open: the host follows one on the first stream of it and
* lets it go with the last, and every change it tells reaches every stream
* of it. How many streams stand open of each is all else held here; the
* notices (`@luke/host/agent-notices`) watch what waits across the account
* on a read of their own.
*/

import type {
CodingAgentAgentInboxParams,
CodingAgentChatAnswerView,
CodingAgentChatAttachmentParams,
CodingAgentChatChanged,
CodingAgentChatPostParams,
CodingAgentChatWriteAnswerView,
CodingAgentInboxAnsweredView,
CodingAgentInboxAnswerParams,
CodingAgentInboxAnswerView,
CodingAgentInboxAttachmentParams,
CodingAgentInboxAttachmentView,
CodingAgentInboxChanged,
CodingAgentInboxCloseParams,
CodingAgentInboxMessagedView,
CodingAgentInboxMessageParams,
CodingAgentInboxParams,
CodingAgentInboxReadAllView,
CodingAgentInboxReadStateParams,
CodingAgentInboxReadStateView,
Expand All @@ -46,10 +42,6 @@ import { followedStream, type OpenFollows } from "./followed-stream.js";
/** What the Inbox and chat procedures reach: the host's asks of the service. */
export interface AgentInboxProceduresDependencies {
host: {
codingAgentInbox(params: CodingAgentInboxParams): Effect.Effect<CodingAgentInboxAnswerView>;
codingAgentAgentInbox(
params: CodingAgentAgentInboxParams,
): Effect.Effect<CodingAgentInboxAnswerView>;
/** A task's or agent's inbox followed at the host, heard through `onInboxChanged`; following again starts over. */
codingAgentInboxFollow(scope: CodingAgentInboxScope): Effect.Effect<void>;
codingAgentInboxUnfollow(scope: CodingAgentInboxScope): Effect.Effect<void>;
Expand Down Expand Up @@ -189,18 +181,6 @@ export function agentInboxProcedures(dependencies: AgentInboxProceduresDependenc
return made;
};
return {
[AGENT_INBOX_ACT_KIND.CODING_AGENTS_INBOX]: actPort[
AGENT_INBOX_ACT_KIND.CODING_AGENTS_INBOX
].effect(function* ({ input, context, errors }) {
yield* refuseUnlessPanel(context.actor, () => errors.REFUSED());
return yield* host.codingAgentInbox(input);
}),
[AGENT_INBOX_ACT_KIND.CODING_AGENTS_AGENT_INBOX]: actPort[
AGENT_INBOX_ACT_KIND.CODING_AGENTS_AGENT_INBOX
].effect(function* ({ input, context, errors }) {
yield* refuseUnlessPanel(context.actor, () => errors.REFUSED());
return yield* host.codingAgentAgentInbox(input);
}),
[AGENT_INBOX_ACT_KIND.CODING_AGENTS_INBOX_FOLLOW]: actPort[
AGENT_INBOX_ACT_KIND.CODING_AGENTS_INBOX_FOLLOW
].effect(function* ({ input, context, errors }) {
Expand Down
Loading
Loading