Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
bc19431
feat(io): add interactive terminal lifecycle
aidandaly24 Sep 3, 2026
24e41e8
feat(tui): add post-render handoff
aidandaly24 Sep 3, 2026
af42992
feat(runtime): add interactive shell transport
aidandaly24 Sep 3, 2026
9bbcf4e
feat(runtime): add shell command and TUI flow
aidandaly24 Sep 3, 2026
6cbfaa4
docs(runtime): document interactive shell
aidandaly24 Sep 3, 2026
18a74ba
fix(build): bundle runtime shell client only
aidandaly24 Sep 3, 2026
f40348f
test(runtime): pin shell command region
aidandaly24 Sep 3, 2026
f895e83
feat(tui): return results from handoffs
aidandaly24 Sep 3, 2026
77dc231
feat(tui): remount after resumable handoffs
aidandaly24 Sep 3, 2026
9160e64
feat(runtime): return shell TUI to its origin
aidandaly24 Sep 3, 2026
d37ad2a
test(runtime): allow TUI remount under coverage
aidandaly24 Sep 3, 2026
6ea324a
refactor(io): remove explicit terminal detach
aidandaly24 Sep 3, 2026
f176bff
refactor(runtime): target published shell session API
aidandaly24 Sep 3, 2026
4d7d073
refactor(runtime): remove shell reattach inputs
aidandaly24 Sep 3, 2026
eeb14eb
refactor(runtime): remove detach lifecycle messaging
aidandaly24 Sep 3, 2026
087d24e
docs(runtime): defer shell reattach workflow
aidandaly24 Sep 3, 2026
47a4bb8
test(runtime): allow shell picker rendering under coverage
aidandaly24 Sep 3, 2026
d67550b
fix(tui): force interactive rendering for TTY sessions
aidandaly24 Sep 3, 2026
6888d1f
fix(io): preserve UTF-8 terminal input
aidandaly24 Sep 3, 2026
b1ff89d
build: expose shell protocol SDK exports
aidandaly24 Sep 3, 2026
92df6e4
refactor(runtime): derive shell transport from SDK
aidandaly24 Sep 3, 2026
df026ce
fix(runtime): report shell reconnect outcome
aidandaly24 Sep 3, 2026
7c206df
refactor(runtime): suspend Ink during shell sessions
aidandaly24 Sep 3, 2026
5c82949
refactor(tui): remove custom shell handoff
aidandaly24 Sep 3, 2026
79858dc
test(runtime): wait for returned TUI input handler
aidandaly24 Sep 3, 2026
afbfd61
test(runtime): retry TUI exit after route return
aidandaly24 Sep 4, 2026
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
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ agentcore # interactive TUI
│ ├── get # fetch a Runtime by id
│ ├── list # list Runtimes (server-side paginated)
│ ├── invoke # invoke a Runtime headlessly or in a persistent console
│ ├── shell # open a persistent interactive terminal in a Runtime
│ ├── logs # follow a Runtime's logs live, or search a time window
│ ├── traces
│ │ ├── list # list a Runtime's recent traces
Expand Down Expand Up @@ -517,6 +518,38 @@ accept ARNs, `--version`, `--interactive`, cross-account targets, or custom
request paths. All requests use the Runtime `/invocations` route, including MCP
Runtimes.

### Open a Runtime shell

Runtime Shell opens a persistent interactive terminal in a Runtime session.
Bare shell opens the Runtime and endpoint pickers. `--id` skips the Runtime
picker, and `--id` plus `--qualifier` connects directly.

```bash
agentcore runtime shell
agentcore runtime shell --id <runtimeId>
agentcore runtime shell --id <runtimeId> --qualifier DEFAULT
```

Use `--session-id` to open the shell in a specific Runtime session/VM:

```bash
agentcore runtime shell \
--id <runtimeId> \
--qualifier DEFAULT \
--session-id <runtimeSessionId>
```

CUSTOM_JWT Runtimes require `--bearer-token`. Interactive bearer tokens may be
inline or `file://` sources, but not stdin.

The shell forwards terminal input byte-for-byte, including `Ctrl+C`, `Ctrl+D`,
escape sequences, and full-screen terminal applications. Terminal resize events
update the remote PTY. Running `exit` or sending `Ctrl+D` terminates the remote
shell.

Runtime Shell requires TTY stdin and stdout and does not support `--json` or
`--endpoint-url`.

Bare Runtime branches and leaves, plus `memory`, `memory get`, and `memory list`,
require a TTY on stdin and stdout.
For Runtime Invoke, supplying a payload or headless-only request or output flags
Expand Down
151 changes: 150 additions & 1 deletion bun.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"@opentelemetry/sdk-metrics": "^2.10.0",
"@smithy/core": "^3.33.3",
"@tanstack/react-query": "^5.101.2",
"bedrock-agentcore": "^0.4.3",
"cli-truncate": "^6.1.1",
"commander": "^15.0.0",
"handlebars": "^4.7.9",
Expand Down
34 changes: 33 additions & 1 deletion scripts/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,37 @@ function assetLoaderPlugin(): Bun.BunPlugin {
};
}

function runtimeShellSdkPlugin(): Bun.BunPlugin {
const runtimeEntry = Bun.resolveSync("bedrock-agentcore/runtime", REPO_ROOT);
const runtimeDirectory = resolve(runtimeEntry, "..");
const runtimeClient = join(runtimeDirectory, "client.js");
const shellProtocol = join(runtimeDirectory, "shell", "protocol.js");
const namespace = "runtime-shell-sdk";

return {
name: "runtime-shell-sdk-client",
setup(build) {
build.onResolve({ filter: /^bedrock-agentcore\/runtime$/ }, () => ({
path: "runtime",
namespace,
}));
build.onResolve({ filter: /^runtime-shell-sdk\/client$/ }, () => ({
path: runtimeClient,
}));
build.onResolve({ filter: /^runtime-shell-sdk\/protocol$/ }, () => ({
path: shellProtocol,
}));
build.onLoad({ filter: /^runtime$/, namespace }, () => ({
contents: [
'export { RuntimeClient } from "runtime-shell-sdk/client";',
'export { ShellChannel, MAX_FRAME_SIZE } from "runtime-shell-sdk/protocol";',
].join("\n"),
loader: "js",
}));
},
};
}

function bootstrapTemplate(): string {
const manifest = Bun.resolveSync("@aws-cdk/toolkit-lib/package.json", REPO_ROOT);
const template = join(resolve(manifest, ".."), ...BOOTSTRAP_TEMPLATE);
Expand Down Expand Up @@ -133,6 +164,7 @@ async function bundle(): Promise<void> {
minify: MINIFY,
define: DEFINE,
external: EXTERNAL,
plugins: [runtimeShellSdkPlugin()],
});
const bin = join(DIST, "index.js");
await Bun.write(bin, BIN_LOADER);
Expand Down Expand Up @@ -165,7 +197,7 @@ async function compile(target: string): Promise<void> {
define: DEFINE,
root: REPO_ROOT,
naming: { asset: ASSET_NAMING },
plugins: [assetLoaderPlugin()],
plugins: [assetLoaderPlugin(), runtimeShellSdkPlugin()],
});
await assertTemplateIsEmbedded(outfile, template);
console.log(
Expand Down
13 changes: 13 additions & 0 deletions src/components/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { MemoryScreen } from "../handlers/memory/screen.tsx";
import { MemoryGetJsonScreen, MemoryGetScreen } from "../handlers/memory/get/screen.tsx";
import { MemoryListScreen } from "../handlers/memory/list/screen.tsx";
import { RuntimeInvokeScreen } from "../handlers/runtime/invoke/screen.tsx";
import { RuntimeShellScreen } from "../handlers/runtime/shell/screen.tsx";
import { EvalScreen } from "../handlers/eval/screen.tsx";
import { EvaluatorScreen } from "../handlers/eval/evaluator/screen.tsx";
import { EvaluatorListScreen } from "../handlers/eval/evaluator/list/screen.tsx";
Expand Down Expand Up @@ -378,6 +379,18 @@ export function Root({ path, ctx, core, queryClient }: RootProps) {
path="agentcore/runtime/invoke/:runtimeId/:qualifier"
element={<RuntimeInvokeScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/runtime/shell"
element={<RuntimeShellScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/runtime/shell/:runtimeId"
element={<RuntimeShellScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/runtime/shell/:runtimeId/:qualifier"
element={<RuntimeShellScreen ctx={ctx} core={core} />}
/>
<Route path="agentcore/gateway" element={<GatewayScreen ctx={ctx} core={core} />} />
<Route
path="agentcore/gateway/get"
Expand Down
9 changes: 8 additions & 1 deletion src/core/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { MemoryClient } from "./memory";
import { PolicyClient } from "./policy";
import { ObservabilityClient } from "./observability";
import { RuntimeClient } from "./runtime";
import type { OpenRuntimeShell } from "./runtime";
import { FsReadWriteJson } from "../io";
import type {
AwsClients,
Expand Down Expand Up @@ -49,6 +50,7 @@ type CoreClientConfig = {
newSessionId?: () => string;
now?: () => number;
bedrockAgentImporter?: CoreBedrockAgentImporter;
openRuntimeShell?: OpenRuntimeShell;
};

// CoreClient is the single entry point to the Bedrock AgentCore APIs. It owns the
Expand Down Expand Up @@ -89,7 +91,12 @@ export class CoreClient implements AwsClients {
this.logger = config.logger;
const fetch = config.fetch ?? globalThis.fetch;
this.fetch = fetch;
this.runtime = new RuntimeClient(this, fetch, this.logger.child({ module: "runtime" }));
this.runtime = new RuntimeClient(
this,
fetch,
this.logger.child({ module: "runtime" }),
config.openRuntimeShell,
);
this.gateway = new GatewayClient(this, fetch, this.logger.child({ module: "gateway" }));
this.policy = new PolicyClient(this, this.logger.child({ module: "policy" }));
// EvalClient shares the injected fetch: dataset content is served from a
Expand Down
17 changes: 17 additions & 0 deletions src/core/runtime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,27 @@ import type {
CoreRuntimeClient,
RuntimeInvokeRequest,
RuntimeInvokeResponse,
RuntimeShellRequest,
RuntimeShellSession,
} from "../handlers/runtime/types";
import type { Logger } from "../logging";
import type { AwsClients, CoreFetch, CoreOptions } from "./types";
import { invokeRuntime } from "./invokeRuntime";
import { toClientConfig } from "./utils";

export type OpenRuntimeShell = (
request: RuntimeShellRequest,
options: CoreOptions,
) => Promise<RuntimeShellSession>;

export class RuntimeClient implements CoreRuntimeClient {
constructor(
private readonly clients: AwsClients,
private readonly fetch: CoreFetch,
private readonly logger: Logger,
private readonly openShell: OpenRuntimeShell = async () => {
throw new Error("Runtime shell transport is not configured");
},
) {}

invokeRuntime(
Expand All @@ -40,6 +50,13 @@ export class RuntimeClient implements CoreRuntimeClient {
);
}

openRuntimeShell(
request: RuntimeShellRequest,
options: CoreOptions,
): Promise<RuntimeShellSession> {
return this.openShell(request, options);
}

async getRuntime(
id: string,
options: CoreOptions,
Expand Down
Loading
Loading