Skip to content
Open
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
17 changes: 14 additions & 3 deletions apps/docs/content/docs/cli/cli-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -947,12 +947,23 @@ superset terminals create --workspace ws_…
{ flag: "--workspace <id>", required: true, description: "Workspace UUID." },
{ flag: "--host <id>", description: "Host the workspace lives on (default: this machine)." },
]}
output={`{
sessions: Array<TerminalSession>;
output={`{
sessions: Array<TerminalSession & {
agentStatus?: {
agentId: string;
sessionId?: string;
definitionId?: string;
startedAt: number;
lastEventType: "Start" | "Stop" | "PermissionRequest" | "Failed" | "Attached" | "Detached";
lastEventAt: number;
};
}>;
}`}
>
List the live terminal sessions in a workspace. Presence in the list means the
PTY exists, not that an agent inside it is working or idle.
PTY exists. When Superset has identified a live agent inside it, `agentStatus`
reports the same normalized lifecycle state used by the workspace board;
`PermissionRequest` means the agent needs attention.
</Command>

<Command
Expand Down
20 changes: 20 additions & 0 deletions apps/docs/content/docs/sdk/reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,26 @@ const { terminalId } = await client.terminals.create({
});
```

### `terminals.list({ hostId, workspaceId })`

List the live terminal sessions in a workspace. When Superset has identified
an agent in a terminal, its row includes `agentStatus` with the agent and
session identity and the latest normalized lifecycle event. A
`PermissionRequest` event means the agent is waiting for input or approval.

```ts
const { sessions } = await client.terminals.list({
hostId: '<machineId>',
workspaceId: '<uuid>',
});

for (const terminal of sessions) {
if (terminal.agentStatus?.lastEventType === 'PermissionRequest') {
console.log(`${terminal.agentStatus.agentId} needs attention`);
}
}
```

---

## automations
Expand Down
36 changes: 32 additions & 4 deletions packages/host-service/src/trpc/router/terminal/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,39 @@ export const terminalRouter = router({
})
.optional(),
)
.query(async ({ ctx, input }) => ({
sessions: await listLiveTerminalSessions(ctx.db, {
.query(async ({ ctx, input }) => {
const sessions = await listLiveTerminalSessions(ctx.db, {
workspaceId: input?.workspaceId,
}),
})),
});
const agentsByTerminal = new Map(
(input?.workspaceId
? ctx.terminalAgentStore.listByWorkspace(input.workspaceId)
: []
).map((binding) => [binding.terminalId, binding] as const),
);

return {
sessions: sessions.map((session) => {
const binding = agentsByTerminal.get(session.terminalId);
if (!binding) return session;
return {
...session,
agentStatus: {
agentId: binding.agentId,
...(binding.agentSessionId
? { sessionId: binding.agentSessionId }
: {}),
...(binding.definitionId
? { definitionId: binding.definitionId }
: {}),
startedAt: binding.startedAt,
lastEventAt: binding.lastEventAt,
lastEventType: binding.lastEventType,
},
};
}),
};
}),

hasRunningProcess: protectedProcedure
.input(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ describe("terminal router integration", () => {
).rejects.toBeInstanceOf(TRPCClientError);
});

test("createSession sends the configured shell to the daemon instead of inherited bash", async () => {
test("createSession uses the configured shell and list includes live agent status", async () => {
const tmp = mkdtempSync(join(tmpdir(), "host-service-terminal-shell-"));
const socketPath = join(tmp, "pty-daemon.sock");
const fakeFishPath = join(tmp, "fish");
Expand Down Expand Up @@ -145,6 +145,25 @@ describe("terminal router integration", () => {
expect(meta.argv[1]).toBe("--init-command");
expect(meta.env?.SHELL).toBe(fakeFishPath);
expect(meta.env?.SUPERSET_TERMINAL_ID).toBe(terminalId);

await scenario.host.unauthenticatedTrpc.notifications.hook.mutate({
terminalId,
eventType: "request_user_input",
agent: { agentId: "codex", sessionId: "thread-123" },
});
const listedWithAgent = await scenario.host.trpc.terminal.list.query({
workspaceId: scenario.workspaceId,
});
const terminal = listedWithAgent.sessions.find(
(session) => session.terminalId === terminalId,
);
expect(terminal?.agentStatus).toMatchObject({
agentId: "codex",
sessionId: "thread-123",
lastEventType: "PermissionRequest",
});
expect(terminal?.agentStatus?.startedAt).toBeNumber();
expect(terminal?.agentStatus?.lastEventAt).toBeNumber();
} finally {
await scenario.host.trpc.terminal.killSession
.mutate({
Expand Down
4 changes: 4 additions & 0 deletions packages/sdk/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ import {
TaskUpdateParams,
} from "./resources/tasks";
import {
TerminalAgentLifecycleEventType,
TerminalAgentStatus,
TerminalCloseParams,
TerminalCloseResult,
TerminalCreateParams,
Expand Down Expand Up @@ -1282,6 +1284,8 @@ export declare namespace Superset {

export {
Terminals,
TerminalAgentLifecycleEventType,
TerminalAgentStatus,
TerminalCreateParams,
TerminalCreateResult,
TerminalListParams,
Expand Down
2 changes: 2 additions & 0 deletions packages/sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ export {
Tasks,
type TaskUpdateParams,
type TerminalCloseParams,
type TerminalAgentLifecycleEventType,
type TerminalAgentStatus,
type TerminalCloseResult,
type TerminalCreateParams,
type TerminalCreateResult,
Expand Down
2 changes: 2 additions & 0 deletions packages/sdk/src/resources/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export {
} from "./organization";
export { type Project, type ProjectListResponse, Projects } from "./projects";
export {
type TerminalAgentLifecycleEventType,
type TerminalAgentStatus,
type TerminalCloseParams,
type TerminalCloseResult,
type TerminalCreateParams,
Expand Down
21 changes: 21 additions & 0 deletions packages/sdk/src/resources/terminals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,25 @@ export interface TerminalSummary {
exitCode: number;
attached: boolean;
title: string | null;
/** Present when Superset has identified a live agent in this terminal. */
agentStatus?: TerminalAgentStatus;
}

export type TerminalAgentLifecycleEventType =
| "Start"
| "Stop"
| "PermissionRequest"
| "Failed"
| "Attached"
| "Detached";

export interface TerminalAgentStatus {
agentId: string;
sessionId?: string;
definitionId?: string;
startedAt: number;
lastEventAt: number;
lastEventType: TerminalAgentLifecycleEventType;
}

export interface TerminalListResult {
Expand Down Expand Up @@ -185,6 +204,8 @@ export declare namespace Terminals {
TerminalListParams,
TerminalListResult,
TerminalSummary,
TerminalAgentStatus,
TerminalAgentLifecycleEventType,
TerminalSendParams,
TerminalSendResult,
TerminalReadParams,
Expand Down