Skip to content

Commit c150e54

Browse files
committed
fix session polling and release snapshots
Change-Id: I160f3059d6378228f440e0bd5b9bd6b0dc7b84fc
1 parent 65ccfac commit c150e54

15 files changed

Lines changed: 97 additions & 46 deletions

File tree

.github/workflows/release.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ jobs:
6262
if: github.event_name == 'workflow_dispatch' && inputs.channel == 'beta'
6363
run: >-
6464
bun run scripts/release/snapshot.ts apply
65-
--run-id "${{ github.run_id }}"
6665
--sha "${{ github.sha }}"
6766
6867
- name: Validate channel, branch, and package versions

docs/getting-started.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ $ agents apply -y
131131

132132
## Run a session
133133

134-
A **session** is a runtime conversation started from a managed agent. `agents session run` creates a session, sends a prompt, and streams the response:
134+
A **session** is a runtime conversation started from a managed agent. `agents session run` creates a session, sends a prompt, and polls until the response completes. Add `--stream` to stream live events over SSE:
135135

136136
```bash
137137
agents session run "Summarize the repo structure" --agent assistant

docs/getting-started.zh-CN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ $ agents apply -y
131131

132132
## 运行 session
133133

134-
**session** 是从一个已托管 agent 启动的运行时对话。`agents session run` 创建 session、发送 prompt 并流式返回
134+
**session** 是从一个已托管 agent 启动的运行时对话。`agents session run` 创建 session、发送 prompt,并默认轮询至响应完成;如需通过 SSE 实时返回事件,请添加 `--stream`
135135

136136
```bash
137137
agents session run "Summarize the repo structure" --agent assistant

docs/guides/run-sessions.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ A **session** is a runtime conversation started from a managed agent. Sessions a
1212
agents session run "Summarize the repo structure" --agent assistant
1313
```
1414

15-
`session run` creates a session, sends the prompt, and streams the response. When only one agent is configured, `--agent` is auto-detected. For a Qoder agent with `delivery.qoder.type: forward`, Identity is optional: without one OpenCMA looks up the enabled Identity whose `external_id` is `__qca_admin_identity__` and sends its real `idn_...` id. Configure `defaults.session.qoder.identity_id` or pass `--identity-id` to select an existing business Identity. OpenCMA never creates or updates Identity resources implicitly.
15+
`session run` creates a session, sends the prompt, and polls until the response completes. Pass `--stream` to receive live events over SSE. When only one agent is configured, `--agent` is auto-detected. For a Qoder agent with `delivery.qoder.type: forward`, Identity is optional: without one OpenCMA looks up the enabled Identity whose `external_id` is `__qca_admin_identity__` and sends its real `idn_...` id. Configure `defaults.session.qoder.identity_id` or pass `--identity-id` to select an existing business Identity. OpenCMA never creates or updates Identity resources implicitly.
1616

1717
Options:
1818

@@ -26,7 +26,7 @@ Options:
2626
| `--title <title>` | Session title. |
2727
| `--provider <name>` | Target provider (required for multi-provider agents). |
2828
| `--json` | Output events as JSONL. |
29-
| `--no-stream` | Use polling instead of SSE streaming. |
29+
| `--stream` | Use SSE streaming instead of the default polling mode. |
3030

3131
## Continue an existing session
3232

docs/reference/cli.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,12 @@ Manage runtime agent sessions.
106106
| `session create [agent-name]` | Create a new session. |
107107
| `session list` | List sessions from the provider. |
108108
| `session get <session-id>` | Get details of a session. |
109-
| `session run <prompt-or-agent> [prompt]` | Create a session, send a message, and stream the response. |
110-
| `session send <session-id> <message>` | Send a message to an existing session and stream the response. |
109+
| `session run <prompt-or-agent> [prompt]` | Create a session, send a message, and poll until the response completes. |
110+
| `session send <session-id> <message>` | Send a message to an existing session and poll until the response completes. |
111111
| `session events <session-id>` | List event history for a session. |
112112
| `session delete <session-id>` | Delete a session. |
113113

114-
`session create` / `session run` accept `--agent`, `--identity-id`, `--environment`, `--vault`, `--memory-stores`, `--title`, and `--provider`. `--identity-id` selects an existing Qoder Forward Identity and overrides `defaults.session.qoder.identity_id`; when neither is provided, OpenCMA resolves the Identity whose `external_id` is `__qca_admin_identity__`. OpenCMA never creates or updates an Identity. `session run` and `session send` accept `--json` (JSONL output) and `--no-stream` (polling instead of SSE). `session list` accepts `--agent` and `--all`; `session events` accepts `--limit`, `--all`, `--json`.
114+
`session create` / `session run` accept `--agent`, `--identity-id`, `--environment`, `--vault`, `--memory-stores`, `--title`, and `--provider`. `--identity-id` selects an existing Qoder Forward Identity and overrides `defaults.session.qoder.identity_id`; when neither is provided, OpenCMA resolves the Identity whose `external_id` is `__qca_admin_identity__`. OpenCMA never creates or updates an Identity. `session run` and `session send` use polling by default and accept `--stream` to opt into SSE streaming, plus `--json` for JSON output. `session list` accepts `--agent` and `--all`; `session events` accepts `--limit`, `--all`, `--json`.
115115

116116
## `agents deployment`
117117

packages/cli/src/commands/session.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -267,9 +267,14 @@ interface SessionRunOpts {
267267
title?: string;
268268
provider?: string;
269269
json?: boolean;
270+
stream?: boolean;
270271
noStream?: boolean;
271272
}
272273

274+
export function shouldStreamSession(options: { stream?: boolean; noStream?: boolean }): boolean {
275+
return options.stream === true && options.noStream !== true;
276+
}
277+
273278
export async function sessionRunCommand(
274279
promptOrAgent: string,
275280
promptOrOptions?: string | SessionRunOpts,
@@ -297,36 +302,38 @@ export async function sessionRunCommand(
297302
};
298303

299304
const ctx = await buildCliRuntime(options.file);
300-
const run = options.noStream
301-
? await startSessionRunPolling(ctx, prompt, runOptions)
302-
: await startSessionRun(ctx, prompt, runOptions);
305+
const stream = shouldStreamSession(options);
306+
const run = stream
307+
? await startSessionRun(ctx, prompt, runOptions)
308+
: await startSessionRunPolling(ctx, prompt, runOptions);
303309
const session = run.session;
304310
if (!options.json) {
305311
log.success(`Session created: ${chalk.bold(session.id)}`);
306312
}
307313

308-
if (options.noStream) {
309-
renderCollectedEvents(run as Awaited<ReturnType<typeof startSessionRunPolling>>, !!options.json);
310-
} else {
314+
if (stream) {
311315
await streamAndRender((run as Awaited<ReturnType<typeof startSessionRun>>).events, !!options.json);
316+
} else {
317+
renderCollectedEvents(run as Awaited<ReturnType<typeof startSessionRunPolling>>, !!options.json);
312318
}
313319
}
314320

315321
interface SessionSendOpts {
316322
file: string;
317323
provider?: string;
318324
json?: boolean;
325+
stream?: boolean;
319326
noStream?: boolean;
320327
}
321328

322329
export async function sessionSendCommand(sessionId: string, message: string, options: SessionSendOpts) {
323330
const ctx = await buildCliRuntime(options.file);
324-
if (options.noStream) {
325-
const result = await sendSessionMessagePolling(ctx, sessionId, message, { provider: options.provider });
326-
renderCollectedEvents(result, !!options.json);
327-
} else {
331+
if (shouldStreamSession(options)) {
328332
const events = await sendSessionMessageStreaming(ctx, sessionId, message, { provider: options.provider });
329333
await streamAndRender(events, !!options.json);
334+
} else {
335+
const result = await sendSessionMessagePolling(ctx, sessionId, message, { provider: options.provider });
336+
renderCollectedEvents(result, !!options.json);
330337
}
331338
}
332339

packages/cli/src/program.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ sessionCmd
229229

230230
sessionCmd
231231
.command("run <prompt-or-agent> [prompt]")
232-
.description("Create a session, send a message, and stream the response")
232+
.description("Create a session, send a message, and wait for the response")
233233
.addOption(configFileOption())
234234
.option("--agent <name>", "Agent name (auto-detected when only one agent is configured)")
235235
.option("--identity-id <id>", "Override the configured Qoder Forward Identity")
@@ -242,16 +242,18 @@ sessionCmd
242242
.option("--title <title>", "Session title")
243243
.addOption(providerOption("Target provider"))
244244
.option("--json", "Output events as JSONL")
245-
.option("--no-stream", "Use polling instead of SSE streaming")
245+
.addOption(new Option("--stream", "Stream events over SSE instead of polling").conflicts("noStream"))
246+
.addOption(new Option("--no-stream", "Use polling (deprecated; polling is now the default)").hideHelp())
246247
.action(withResolvedConfigFile(sessionRunCommand));
247248

248249
sessionCmd
249250
.command("send <session-id> <message>")
250-
.description("Send a message to an existing session and stream the response")
251+
.description("Send a message to an existing session and wait for the response")
251252
.addOption(configFileOption())
252253
.addOption(providerOption("Target provider"))
253254
.option("--json", "Output events as JSONL")
254-
.option("--no-stream", "Use polling instead of SSE streaming")
255+
.addOption(new Option("--stream", "Stream events over SSE instead of polling").conflicts("noStream"))
256+
.addOption(new Option("--no-stream", "Use polling (deprecated; polling is now the default)").hideHelp())
255257
.action(withResolvedConfigFile(sessionSendCommand));
256258

257259
sessionCmd

packages/cli/tests/unit/cli-contracts.test.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,16 @@ test("session run exposes an explicit Forward identity override", async () => {
172172

173173
expect(result.exitCode).toBe(0);
174174
expect(result.stdout).toContain("--identity-id <id>");
175+
expect(result.stdout).toContain("--stream");
176+
expect(result.stdout).not.toContain("--no-stream");
177+
});
178+
179+
test("session send exposes explicit streaming as an opt-in", async () => {
180+
const result = await runAgents(["session", "send", "--help"]);
181+
182+
expect(result.exitCode).toBe(0);
183+
expect(result.stdout).toContain("--stream");
184+
expect(result.stdout).not.toContain("--no-stream");
175185
});
176186

177187
test("global --file before plan selects the config file", async () => {
@@ -528,11 +538,11 @@ test("json-mode user errors are written to stderr with empty stdout", async () =
528538
expect(result.stderr).toContain("File not found");
529539
});
530540

531-
test("session run reports missing applied resources through the core runtime", async () => {
541+
test("session run defaults to polling and reports missing applied resources through the core runtime", async () => {
532542
const dir = await makeTempDir();
533543
const configPath = await writeConfig(dir);
534544

535-
const result = await runAgents(["session", "run", "assistant", "hello", "--file", configPath, "--no-stream"]);
545+
const result = await runAgents(["session", "run", "assistant", "hello", "--file", configPath]);
536546

537547
expect(result.exitCode).toBe(1);
538548
expect(result.stdout).toBe("");

packages/cli/tests/unit/session-event.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,20 @@ import {
44
formatTimestamp,
55
isTerminalSessionStatus,
66
shouldRenderLiveEvent,
7+
shouldStreamSession,
78
} from "../../src/commands/session.ts";
89

10+
describe("session transport selection", () => {
11+
test("uses polling by default for every provider", () => {
12+
expect(shouldStreamSession({})).toBe(false);
13+
expect(shouldStreamSession({ noStream: true })).toBe(false);
14+
});
15+
16+
test("uses SSE only when explicitly requested", () => {
17+
expect(shouldStreamSession({ stream: true })).toBe(true);
18+
});
19+
});
20+
921
describe("session live rendering", () => {
1022
test("suppresses user-message echoes", () => {
1123
expect(

scripts/release/channel.test.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,12 @@ describe("release channel guard", () => {
1313
});
1414

1515
test("accepts deterministic beta snapshots only on main", () => {
16-
expect(validateReleaseIdentity("beta", "main", "0.0.0-beta.run-123456789.sha-a1b2c3d")).toEqual({
16+
expect(validateReleaseIdentity("beta", "main", "1.2.3-beta-a1b2c3d-20260720")).toEqual({
1717
channel: "beta",
18-
version: "0.0.0-beta.run-123456789.sha-a1b2c3d",
18+
version: "1.2.3-beta-a1b2c3d-20260720",
1919
distTag: "beta",
2020
});
21-
expect(() => validateReleaseIdentity("beta", "feature/test", "0.0.0-beta.run-123456789.sha-a1b2c3d")).toThrow(
22-
"main",
23-
);
21+
expect(() => validateReleaseIdentity("beta", "feature/test", "1.2.3-beta-a1b2c3d-20260720")).toThrow("main");
2422
expect(() => validateReleaseIdentity("beta", "main", "1.2.3-beta.0")).toThrow("unexpected format");
2523
});
2624

0 commit comments

Comments
 (0)