Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
128 commits
Select commit Hold shift + click to select a range
4da84e9
feat(managed-agent): admit later Turns of a Workspace-bound Session f…
Sep 30, 2026
1dc0d0c
feat(web-shell): let the creator send later Turns to a bound Session
Sep 30, 2026
aedd7a6
feat(managed-agent): let the bound Session creator cancel a running Turn
Sep 30, 2026
e12dc74
feat(managed-agent): let the bound Session creator rename it
Sep 30, 2026
3df9ee1
style(web-shell): keep the workspaceTurns expression on one line as P…
Sep 30, 2026
a550540
test(managed-agent): read the renamed title from the public Session m…
Sep 30, 2026
b8c5830
fix(web-shell): match the generated optional workspaceTurns capability
Sep 30, 2026
8a058da
Merge origin/main into feat/hosted-bound-later-turns
yiliang114 Sep 30, 2026
f2a028b
test(managed-agent): keep the approval run out of the later-Turn checks
Sep 30, 2026
61ee97e
Merge origin/main into feat/hosted-bound-later-turns
yiliang114 Sep 30, 2026
e96c116
docs(managed-agent): Align G0 gating statements with creator later-Tu…
yiliang114 Sep 30, 2026
2f4abb1
chore(managed-agent): Bump the public API contract to 1.27.0
yiliang114 Sep 30, 2026
af0373c
fix(web-shell): hide execution-unavailable banner line when creator c…
yiliang114 Sep 30, 2026
3a736a3
test(sdk-java): pin bound-Session admission clauses with negative con…
yiliang114 Oct 1, 2026
5d4499c
test(sdk-java): exercise later Turns under approval-mode=default and …
yiliang114 Oct 1, 2026
26de98c
fix(sdk-java): align bound-Session later-Turn admission with the exec…
yiliang114 Oct 1, 2026
f2601d6
Merge origin/main into feat/hosted-bound-later-turns
yiliang114 Oct 1, 2026
dc22300
Merge remote-tracking branch 'origin/main' into feat/hosted-bound-lat…
yiliang114 Oct 1, 2026
66646a6
fix(sdk-java): cancel a live bound Turn through its running attachment
Oct 1, 2026
485c92b
Merge branch 'main' into feat/hosted-bound-later-turns
yiliang114 Oct 1, 2026
9a60cff
fix(managed-agent): stop a bound Turn under refused authorization
Oct 1, 2026
c809439
fix(managed-agent): fail the Turn when a cancel attach is refused
yiliang114 Oct 1, 2026
2431e5e
fix(managed-agent): retire a refused rename command instead of wedgin…
yiliang114 Oct 1, 2026
3f0432b
refactor(managed-agent): drop the dead read-grant operand and fix the…
yiliang114 Oct 1, 2026
ff91ed5
Merge branch 'main' into feat/hosted-bound-later-turns
yiliang114 Oct 1, 2026
31060be
Merge #13112's head into the #13162 follow-up
Oct 1, 2026
f2f8752
refactor(managed-agent): cancel an admitted Turn without attaching
Oct 1, 2026
179aa05
docs(managed-agent): state the full later-Turn admission rule and nar…
yiliang114 Oct 1, 2026
8a26cc9
fix(managed-agent): deliver cancellation through the running owner
yiliang114 Oct 1, 2026
925cffe
fix(ci): bound hosted browser dependency installation
yiliang114 Oct 1, 2026
fa31c02
Merge remote-tracking branch 'origin/main' into codex/ci-browser-inst…
yiliang114 Oct 1, 2026
f6fd035
fix(managed-agent): align cancellation test with runtime recovery
yiliang114 Oct 1, 2026
d857f0f
Merge remote-tracking branch 'origin/main' into codex/pr13163-owner-c…
yiliang114 Oct 1, 2026
77373a0
fix(ci): record the ci.yml growth in the workflow size baseline
yiliang114 Oct 1, 2026
4a79dcf
fix(ci): reserve browser smoke time after slow dependency downloads
yiliang114 Oct 1, 2026
1b5d007
merge(ci): give hosted browser acceptance its full test budget
yiliang114 Oct 1, 2026
ba222e2
fix(ci): keep time for smoke after slow hosted installs
yiliang114 Oct 1, 2026
fd81587
Merge commit 'ba222e2429bd870f49064f5ef5cb5dd50daab6cb' into codex/pr…
yiliang114 Oct 1, 2026
9dffa01
test(web-shell): wait for step expansion before selecting next group
yiliang114 Oct 1, 2026
b73ec01
test(ci): align hosted browser timeout contract
yiliang114 Oct 1, 2026
4ba2b45
Merge commit 'b73ec018e92d202907787ebd146e4717241cdcb2' into codex/pr…
yiliang114 Oct 1, 2026
7b53bba
Merge remote-tracking branch 'origin/main' into feat/hosted-bound-lat…
yiliang114 Oct 2, 2026
e5b2e8c
fix(managed-agent): retry transient cancel-attach refusals instead of…
yiliang114 Oct 2, 2026
43be009
fix(managed-agent): retire the rename command row on every answered f…
yiliang114 Oct 2, 2026
dba017b
docs(managed-agent): qualify Workspace cancel contract
yiliang114 Oct 2, 2026
7720f6d
docs(web-shell): scope Session creation capabilities
yiliang114 Oct 2, 2026
71e9007
docs(managed-agent): state the retired-rename-command contract in the…
yiliang114 Oct 2, 2026
fe91f94
test(managed-agent): pin the command half of requireNoOpenOperation
yiliang114 Oct 2, 2026
7275c03
chore: merge origin/main to refresh the lint gate baseline
yiliang114 Oct 2, 2026
7e5cbdc
fix(ci): sync Hosted later-turn branch with main
yiliang114 Oct 2, 2026
bf1bada
test(web-shell): pin the bound-Session Cancel control at the page level
yiliang114 Oct 2, 2026
f04bf98
fix(managed-agent): answer same-key retries from the record, retire r…
yiliang114 Oct 2, 2026
733e457
fix(managed-agent): clear rename admission when harness is disabled
yiliang114 Oct 2, 2026
220b036
fix(managed-agent): repair the two red required lanes on this head
yiliang114 Oct 2, 2026
bf24730
fix(managed-agent): preserve rename receipts and retry cancellation
yiliang114 Oct 2, 2026
ef77ebf
Merge remote-tracking branch 'origin/main' into feat/hosted-bound-lat…
Oct 2, 2026
b9b4da4
test(managed-agent): expect the closed Session status for a queued bo…
Oct 2, 2026
c3925ff
Merge origin/main into fix/13162-cancel-refused-authorization
yiliang114 Oct 2, 2026
3cd09be
test(managed-agent): read the renamed Session title from metadata
yiliang114 Oct 2, 2026
22a4012
Merge branch 'main' into feat/hosted-bound-later-turns
yiliang114 Oct 2, 2026
9c0bcf4
fix(managed-agent): restore admission error precedence in CI
yiliang114 Oct 2, 2026
03bdd6c
fix(managed-agent): keep the Workspace refusal ahead of the Harness gate
yiliang114 Oct 2, 2026
8953b8f
fix(runtime-broker): close drained lost workspace bindings
yiliang114 Oct 2, 2026
6c5bdee
Merge main into feat/hosted-bound-later-turns
yiliang114 Oct 2, 2026
fb7296c
merge: preserve hosted admission with current workspace retention
yiliang114 Oct 2, 2026
d20a189
merge: retain concurrent PR branch synchronization
yiliang114 Oct 2, 2026
f1387d8
merge: sync cancellation PR with current main
yiliang114 Oct 2, 2026
a745197
fix(managed-agent): retain replay authority while syncing later-turn …
yiliang114 Oct 2, 2026
61ce828
merge: sync refused-authorization cancellation with main
yiliang114 Oct 3, 2026
9cb70b7
Merge branch 'main' into fix/13162-cancel-refused-authorization
yiliang114 Oct 3, 2026
3363fff
docs(managed-agent): state the cancel rule and refusal codes the code…
yiliang114 Oct 3, 2026
2f0d389
fix(managed-agent): answer bound replays only to the creator and keep…
Oct 3, 2026
1701827
fix(managed-agent): keep a retired rename from reverting a newer title
yiliang114 Oct 3, 2026
41cb651
style(web-shell): format the bound Cancel page test with Prettier
Oct 3, 2026
262eb3a
style(web-shell): apply prettier to ManagedSessionsPage.test.tsx
yiliang114 Oct 3, 2026
29e4d17
fix(managed-agent): recover cold-cache cancels without new-work grants
yiliang114 Oct 3, 2026
60030e4
docs(managed-agent): include storage in both capability descriptions
yiliang114 Oct 3, 2026
9fa6b8d
Merge commit 'refs/prheads/13163' into prmerge-13163
yiliang114 Oct 3, 2026
c11d0b5
Merge origin/main into fix/13162-cancel-refused-authorization
yiliang114 Oct 3, 2026
125954a
merge: sync main and preserve cancellation runtime adoption
yiliang114 Oct 3, 2026
52704a9
merge: preserve concurrent PR synchronization
yiliang114 Oct 3, 2026
3683f11
merge: reconcile the unpushed Java admission fix with the live PR head
yiliang114 Oct 3, 2026
8d98903
refactor(managed-agent): delete the review-minted dead surface and it…
yiliang114 Oct 3, 2026
0d4504a
merge: sync main for managed-agent PR
yiliang114 Oct 3, 2026
f8e83fc
fix(managed-agent): dedupe by dropping this PR's own added test, not …
yiliang114 Oct 3, 2026
3c657d6
merge: preserve concurrent PR cleanup
yiliang114 Oct 3, 2026
b27a595
merge: sync PR owner assignment fix
yiliang114 Oct 3, 2026
a18ea0e
fix(serve): retain passive recovery leases on refusal
yiliang114 Oct 4, 2026
762f4a2
Merge origin/main into fix/13162-cancel-refused-authorization
yiliang114 Oct 4, 2026
04b778a
Merge origin/main into fix/13162-cancel-refused-authorization
yiliang114 Oct 4, 2026
288e7fe
Merge origin/main into fix/13162-cancel-refused-authorization
yiliang114 Oct 4, 2026
30f092d
fix(managed-agent): keep the re-registration guard on the page twin
yiliang114 Oct 4, 2026
df8bdc5
fix(managed-agent): keep the Session lifecycle term on cancel admission
yiliang114 Oct 4, 2026
6739048
Merge origin/main into fix/13162-cancel-refused-authorization
yiliang114 Oct 4, 2026
b683a3d
fix(managed-agent): record a passive adoption stranded by a concurren…
yiliang114 Oct 5, 2026
1fbd319
Merge branch 'main' into fix/13162-cancel-refused-authorization
yiliang114 Oct 5, 2026
d210e2d
Merge origin/main into fix/13162-cancel-refused-authorization
yiliang114 Oct 5, 2026
df2547d
Merge branch 'main' into fix/13162-cancel-refused-authorization
yiliang114 Oct 5, 2026
b8f92ce
fix(managed-agent): fence every resident reattach and keep its refusa…
yiliang114 Oct 5, 2026
d71675b
Merge origin/main into fix/13162-cancel-refused-authorization
yiliang114 Oct 5, 2026
b6eff8f
fix(managed-agent): fence the parked passive recovery against teardown
yiliang114 Oct 5, 2026
eb3b933
Merge origin/main into fix/13162-cancel-refused-authorization
yiliang114 Oct 6, 2026
25eb9ae
Merge origin/main into fix/13162-cancel-refused-authorization
yiliang114 Oct 6, 2026
d7aa13a
fix(managed-agent): preserve concurrent recovery and rename ordering
yiliang114 Oct 6, 2026
eeffbb8
Merge main to preserve managed-agent migration order
yiliang114 Oct 6, 2026
6e6da3c
fix(test): import Mockito times in coordinator tests
yiliang114 Oct 6, 2026
13df2a6
Merge origin/main into fix/13162-cancel-refused-authorization
yiliang114 Oct 6, 2026
4b6a2c8
Merge origin/main into fix/13162-cancel-refused-authorization
yiliang114 Oct 7, 2026
684a290
Merge remote-tracking branch 'origin/main' into fix/13162-cancel-refu…
yiliang114 Oct 7, 2026
504ecd9
fix(managed-agent): settle cancelled recovery and refused approvals
yiliang114 Oct 7, 2026
2748c36
chore(repo): retain published PR base synchronization
yiliang114 Oct 7, 2026
f2864f6
fix(serve): preserve live cancellation reattachment
yiliang114 Oct 7, 2026
0227b25
fix(serve): restore resident recovery outcomes
yiliang114 Oct 7, 2026
a9f7fec
fix(managed-agent): retry transient approval mount failures
yiliang114 Oct 7, 2026
3580cd3
Merge origin/main into fix/13162-cancel-refused-authorization
yiliang114 Oct 7, 2026
89aa9d4
fix(managed-agent): renumber the mutation-attempt migration to V51
yiliang114 Oct 7, 2026
3c42bc6
test(managed-agent): expect the V51 mutation-attempt migration in the…
yiliang114 Oct 7, 2026
ce56f43
test(managed-agent): keep Hosted fixture ports distinct
yiliang114 Oct 7, 2026
234037e
fix(managed-agent): retry operator-reversible refusals on the action-…
yiliang114 Oct 8, 2026
2740c23
fix(cli): refuse a resident inapplicable redrive while a prompt slot …
yiliang114 Oct 8, 2026
a4b5452
Merge origin/main into fix/13162-cancel-refused-authorization
yiliang114 Oct 8, 2026
576a39a
test(managed-agent): pin V52 after renumbering the mutation-attempt m…
Oct 8, 2026
00be8ed
fix(managed-agent): preserve approval retries during cold attachment
yiliang114 Oct 8, 2026
f52f123
test(managed-agent): pin the legacy receipt rewrite and the acquire v…
yiliang114 Oct 8, 2026
7867f4b
test(sdk-java): allow heartbeat recovery time after refused detach
yiliang114 Oct 8, 2026
d4cfa63
Merge branch 'main' into fix/13162-cancel-refused-authorization
yiliang114 Oct 8, 2026
0a6ead7
Merge remote-tracking branch 'origin/main' into fix/13162-cancel-refu…
yiliang114 Oct 8, 2026
39267a9
test(hosted): pin teardown and pending rename recovery guards
yiliang114 Oct 8, 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
Prev Previous commit
Next Next commit
feat(web-shell): let the creator send later Turns to a bound Session
The server now admits later Turns from a Workspace-bound Session's
creator, but the Managed panel still hid the composer for every bound
Session and the Java provider forced canSend to false.

- WebShellSessionCapabilities gains workspaceTurns, computed per Session
  and caller with the same rule the submit path enforces (opt-in on,
  caller can read, caller created the Session). The contract and the
  generated WebShell types describe it.
- The Java provider sends for a bound Session only when the service
  reports workspaceTurns, and reports it in the summary only when true.
- The Managed panel shows the composer for such a Session; cancel stays
  hidden for bound Sessions.
- HostedPublicWorkspaceIT checks the capability per caller through the
  WebShell route; the contract test's WebShell capability shape and
  provider/page tests cover the client.
  • Loading branch information
yiliang114
yiliang114 committed Sep 30, 2026
commit 1dc0d0c3307aed0e2e26d1c37ba64c43746f3290
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ public record SessionCapabilities(boolean items, boolean snapshots,
boolean tasks) {
}

public record WebShellSessionCapabilities(boolean tasks) {
public record WebShellSessionCapabilities(boolean tasks,
boolean workspaceTurns) {
}

@JsonInclude(JsonInclude.Include.NON_NULL)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,6 @@ public class ManagedAgentService {
private static final Pattern TURN_CURSOR = Pattern.compile(
"^(0|[1-9][0-9]{0,18}):([A-Za-z0-9_-]{1,64})$");
private static final int TURN_ID_MAX_LENGTH = 64;
// Every Session serves its task list and detail; the tasks come from the
// Stage H records its Session store holds (H0c).
private static final WebShellSessionCapabilities WEB_SHELL_CAPABILITIES =
new WebShellSessionCapabilities(true);
// Catch-up reads of a stream use pages of this size.
static final int STREAM_PAGE = 100;
// A context stays ready until cwd changes arrive (W2).
Expand Down Expand Up @@ -302,7 +298,7 @@ public PublicSession getPublicSession(String tenantId, String actorId,
public WebShellSession getWebShellSession(String tenantId, String actorId,
String sessionId) {
return webShellSession(requireReadableSession(tenantId, actorId,
sessionId));
sessionId), actorId);
}

public PublicList<PublicSession> listPublicSessions(String tenantId,
Expand All @@ -327,8 +323,8 @@ public WebShellPage<WebShellSession> listWebShellSessions(
decoded == null ? null : decoded.updatedAt(),
decoded == null ? null : decoded.sessionId(), limit);
return new WebShellPage<>(page.sessions().stream()
.map(this::webShellSession).toList(), nextCursor(page),
page.hasMore());
.map(session -> webShellSession(session, actorId)).toList(),
nextCursor(page), page.hasMore());
}

/**
Expand Down Expand Up @@ -483,7 +479,8 @@ private PublicSession publicSession(SessionRecord session) {
publicWorkspace(session));
}

private WebShellSession webShellSession(SessionRecord session) {
private WebShellSession webShellSession(SessionRecord session,
String actorId) {
TurnRecord latestTurn = store.findLatestTurn(session.tenantId(),
session.sessionId()).orElse(null);
EventRecord environmentEvent = store.findLatestEnvironmentEvent(
Expand All @@ -494,7 +491,10 @@ private WebShellSession webShellSession(SessionRecord session) {
latestTurn == null ? null : webShellTurn(latestTurn),
webShellEnvironment(environmentEvent),
session.lastSequence(), webShellWorkspace(session),
WEB_SHELL_CAPABILITIES);
// Every Session serves its task list and detail; the tasks
// come from the Stage H records its Session store holds (H0c).
new WebShellSessionCapabilities(true,
maySubmitWorkspaceTurn(session, actorId)));
}

private static WebShellWorkspace webShellWorkspace(SessionRecord session) {
Expand Down Expand Up @@ -643,15 +643,20 @@ String lifecycleDigest(String sessionId, String operation) {
// Everyone else keeps the existing refusal.
private void requireSubmitter(String tenantId, String actorId,
String sessionId) {
SessionRecord session = store.requireSession(tenantId, sessionId);
if (session.workspace() != null
if (!maySubmitWorkspaceTurn(store.requireSession(tenantId, sessionId),
actorId)) {
requireLegacyWorkspace(tenantId, actorId, sessionId);
}
}

private boolean maySubmitWorkspaceTurn(SessionRecord session,
String actorId) {
return session.workspace() != null
&& harness.isWorkspaceFilesAvailable()
&& workspaces.canRead(tenantId, actorId,
&& workspaces.canRead(session.tenantId(), actorId,
session.workspace().getWorkspaceId())
&& workspaces.createdSession(tenantId, actorId, sessionId)) {
return;
}
requireLegacyWorkspace(tenantId, actorId, sessionId);
&& workspaces.createdSession(session.tenantId(), actorId,
session.sessionId());
}

void requireLegacyWorkspace(String tenantId, String actorId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3870,6 +3870,11 @@
"default": false,
"x-qwen-implementation-status": "planned"
},
"workspaceTurns": {
"type": "boolean",
"default": false,
"description": "True when the caller may submit later Turns to this Workspace-bound Session: the deployment enables Workspace files and the caller created the Session. False for every other caller and for unbound Sessions, which do not use it."
},
"tasks": {
"type": "boolean"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ void publicCreationRunsFilesThroughProductionWorkspaceBinding() throws Exception
assertThat(request("POST", "/v1/agents/sessions/" + session + "/events", later,
"reader-later-" + workspace, "reader", 409).path("error").path("code").asText())
.isEqualTo("workspace_unavailable");
// WebShell advertises the same rule, per caller.
for (String caller : List.of("actor", "reader")) {
assertThat(request("POST", "/api/agent/web-shell/v1/sessions/get", Map.of("sessionId", session),
null, caller, 200).path("capabilities").path("workspaceTurns").asBoolean())
.as(caller).isEqualTo("actor".equals(caller));
}
String laterTurn = request("POST", "/v1/agents/sessions/" + session + "/events", later,
"later-" + workspace, "actor", 202).path("turn_id").asText();
assertThat(laterTurn).isNotBlank();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1176,8 +1176,8 @@ void bothSurfacesReportTheSameSession() throws Exception {
assertThat(other.get("lastSequence").asLong())
.isPositive()
.isEqualTo(session.get("last_event_id").asLong());
assertThat(other.get("capabilities"))
.isEqualTo(json("{\"tasks\":true}"));
assertThat(other.get("capabilities")).isEqualTo(json(
"{\"tasks\":true,\"workspaceTurns\":false}"));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,31 @@ describe('ManagedSessionsPage', () => {
expect(container.textContent).not.toContain('Preparing environment');
});

it('lets the creator send a later Turn to a bound Session', async () => {
mocks.client.getSession.mockResolvedValue(
summary('bound', {
activeTurnId: undefined,
workspace: { workspaceId: 'ws-a', cwdRelative: 'services/api' },
capabilities: { canSend: true, canCancel: false, workspaceTurns: true },
Comment thread
yiliang114 marked this conversation as resolved.
}),
);
mocks.client.submitPrompt.mockResolvedValue({
sessionId: 'bound',
turnId: 'p2',
});
await render('bound');

expect(container.querySelector('textarea')).not.toBeNull();
await input('Run it again');
await click('Send');

expect(mocks.client.submitPrompt).toHaveBeenCalledWith(
'bound',
{ text: 'Run it again' },
expect.objectContaining({ idempotencyKey: expect.any(String) }),
);
});

async function click(label: string) {
const button = [...container.querySelectorAll('button')].find(
(item) => item.textContent === label,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ function ManagedSessionsContent({
}
loading={detail.loading}
/>
{!summary?.workspace &&
{(!summary?.workspace || summary.capabilities.workspaceTurns) &&
(!provider.workspaceBinding || (sessionId && summary)) ? (
<form
className="flex shrink-0 flex-col gap-2"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,11 @@ export interface components {
capabilities?: components["schemas"]["WebShellSessionCapabilities"];
};
WebShellSessionCapabilities: {
/**
* @description True when the caller may submit later Turns to this Workspace-bound Session: the deployment enables Workspace files and the caller created the Session. False for every other caller and for unbound Sessions, which do not use it.
* @default false
*/
workspaceTurns: boolean;
tasks: boolean;
};
WebShellSessionPage: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -445,4 +445,39 @@ describe('createJavaManagedAgentProvider', () => {
expect(transcript.olderCursor).toBeUndefined();
expect(transcript.lastEventId).toBe(4);
});

it('lets a bound Session send only when the service allows its caller', async () => {
const bound = {
sessionId: 'bound-1',
status: 'ACTIVE',
createdAt: 1,
updatedAt: 1,
lastSequence: 3,
workspace: { workspaceId: 'ws-a', cwdRelative: '.' },
};
const provider = createJavaManagedAgentProvider({
baseUrl: 'https://product.example',
fetch: vi
.fn<typeof fetch>()
.mockResolvedValueOnce(
jsonResponse({
...bound,
capabilities: { tasks: true, workspaceTurns: true },
}),
)
.mockResolvedValueOnce(
jsonResponse({
...bound,
capabilities: { tasks: true, workspaceTurns: false },
}),
),
});

expect(
(await provider.getSession('bound-1', { clientId: 'c' })).capabilities,
).toEqual({ canSend: true, canCancel: false, workspaceTurns: true });
expect(
(await provider.getSession('bound-1', { clientId: 'c' })).capabilities,
).toEqual({ canSend: false, canCancel: false });
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@ function toSessionSummary(
turnStatus,
);
const sessionActive = session.status.toLowerCase() === 'active';
// A bound Session takes later Turns only from the caller the service
// allows; everything else about a bound Session stays read-only.
const workspaceTurns =
Boolean(session.workspace) &&
session.capabilities?.workspaceTurns === true;
const errorCode =
session.activeTurn?.errorCode ?? session.environment?.errorCode;
return {
Expand All @@ -216,12 +221,14 @@ function toSessionSummary(
runtimeReady: runtimeState === 'ready',
runtimeState,
capabilities: {
canSend: sessionActive && !active && !session.workspace,
canSend:
sessionActive && !active && (!session.workspace || workspaceTurns),
canCancel:
sessionActive &&
active &&
turnStatus !== 'cancelling' &&
!session.workspace,
...(workspaceTurns ? { workspaceTurns: true } : {}),
},
...(errorCode ? { failure: { code: errorCode, message: errorCode } } : {}),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ export interface ManagedAgentSessionSummary {
phase: ManagedAgentSessionPhase;
runtimeReady: boolean;
runtimeState: ManagedAgentRuntimeState;
capabilities: { canSend: boolean; canCancel: boolean };
capabilities: {
canSend: boolean;
canCancel: boolean;
/** The caller may submit later Turns to this Workspace-bound Session. */
workspaceTurns?: boolean;
};
failure?: { code: string; message: string };
}

Expand Down
Loading