();
+ while (current.id !== context.openCodeSessionId) {
+ if (visited.has(current.id) || !current.parentID) {
+ context.ignoredSessionIds.add(sessionId);
+ return undefined;
+ }
+ visited.add(current.id);
+ chain.push(current);
+ if (
+ current.parentID === context.openCodeSessionId ||
+ context.childSessions.has(current.parentID)
+ ) {
+ break;
+ }
+ const parentDirectory =
+ context.childSessions.get(current.parentID)?.directory ?? context.directory;
+ const parentProbe = yield* loadOpenCodeSession(context, current.parentID, parentDirectory);
+ if (parentProbe.kind === "unavailable") {
+ // Leave the id out of the ignore set so a later event can retry.
+ return undefined;
+ }
+ if (parentProbe.kind === "missing") {
+ context.ignoredSessionIds.add(sessionId);
+ return undefined;
+ }
+ current = parentProbe.info;
+ }
+
+ for (const childInfo of chain.toReversed()) {
+ yield* registerChildSession(context, childInfo, event);
+ }
+ const child = context.childSessions.get(sessionId);
+ return child ? ({ sessionId, child } as const) : undefined;
+ });
+
const emitUnexpectedExit = Effect.fn("emitUnexpectedExit")(function* (
context: OpenCodeSessionContext,
message: string,
@@ -696,12 +1316,26 @@ export function makeOpenCodeAdapter(
}
const turnId = context.activeTurnId;
sessions.delete(context.session.threadId);
+ context.rootStatus = "idle";
+ yield* abortOpenCodeSessions(context);
+ for (const child of context.childSessions.values()) {
+ if (child.active && !child.terminal) {
+ yield* emitChildTaskCompleted(
+ context,
+ child,
+ { type: "session.exited", message },
+ "failed",
+ message,
+ );
+ }
+ }
+ yield* settlePendingOpenCodeRequests(context, undefined, message);
// Emit lifecycle events BEFORE tearing down the scope. Both call sites
// run this inside a fiber forked via `Effect.forkIn(context.sessionScope)`;
// closing that scope triggers the fiber-interrupt finalizer, so any
// subsequent yield point would unwind and silently drop these emits.
yield* emit({
- ...(yield* buildEventBase({
+ ...(yield* buildContextEventBase(context, context.openCodeSessionId, {
threadId: context.session.threadId,
turnId,
})),
@@ -712,7 +1346,7 @@ export function makeOpenCodeAdapter(
},
}).pipe(Effect.ignore);
yield* emit({
- ...(yield* buildEventBase({
+ ...(yield* buildContextEventBase(context, context.openCodeSessionId, {
threadId: context.session.threadId,
turnId,
})),
@@ -723,12 +1357,9 @@ export function makeOpenCodeAdapter(
exitKind: "error",
},
}).pipe(Effect.ignore);
- // Inline the teardown that `stopOpenCodeContext` would do; we can't
+ // Inline the scope close that `stopOpenCodeContext` would do; we can't
// delegate to it because our `getAndSet` above already flipped the
// one-shot guard, so the call would no-op.
- yield* runOpenCodeSdk("session.abort", () =>
- context.client.session.abort({ sessionID: context.openCodeSessionId }),
- ).pipe(Effect.ignore({ log: true }));
yield* Scope.close(context.sessionScope, Exit.void);
});
@@ -737,6 +1368,7 @@ export function makeOpenCodeAdapter(
context: OpenCodeSessionContext,
part: Part,
turnId: TurnId | undefined,
+ providerSessionId: string,
raw: unknown,
) {
const text = textFromPart(part);
@@ -756,7 +1388,7 @@ export function makeOpenCodeAdapter(
}
if (deltaToEmit.length > 0) {
yield* emit({
- ...(yield* buildEventBase({
+ ...(yield* buildContextEventBase(context, providerSessionId, {
threadId: context.session.threadId,
turnId,
itemId: part.id,
@@ -781,7 +1413,7 @@ export function makeOpenCodeAdapter(
) {
context.completedAssistantPartIds.add(part.id);
yield* emit({
- ...(yield* buildEventBase({
+ ...(yield* buildContextEventBase(context, providerSessionId, {
threadId: context.session.threadId,
turnId,
itemId: part.id,
@@ -803,10 +1435,15 @@ export function makeOpenCodeAdapter(
context: OpenCodeSessionContext,
event: OpenCodeSubscribedEvent,
) {
- const payloadSessionId = openCodeEventSessionId(event);
- if (payloadSessionId !== context.openCodeSessionId) {
+ if (yield* Ref.get(context.stopped)) {
return;
}
+ const route = yield* resolveOpenCodeEventSession(context, event);
+ if (!route) {
+ return;
+ }
+ const payloadSessionId = route.sessionId;
+ const child = route.child;
const turnId = context.activeTurnId;
yield* writeNativeEventBestEffort(context.session.threadId, {
@@ -814,7 +1451,7 @@ export function makeOpenCodeAdapter(
event: {
provider: PROVIDER,
threadId: context.session.threadId,
- providerThreadId: context.openCodeSessionId,
+ providerThreadId: payloadSessionId,
type: event.type,
...(turnId ? { turnId } : {}),
payload: event,
@@ -822,11 +1459,14 @@ export function makeOpenCodeAdapter(
});
switch (event.type) {
+ case "session.created":
+ break;
+
case "session.updated": {
const title = openCodeEventSessionTitle(event);
- if (title) {
+ if (title && child === undefined) {
yield* emit({
- ...(yield* buildEventBase({
+ ...(yield* buildContextEventBase(context, payloadSessionId, {
threadId: context.session.threadId,
raw: event,
})),
@@ -838,6 +1478,29 @@ export function makeOpenCodeAdapter(
},
},
});
+ } else if (child !== undefined && !child.terminal) {
+ yield* emitChildTaskProgress(context, child, event, {
+ summary: title ?? "Working",
+ });
+ }
+ break;
+ }
+
+ case "session.deleted": {
+ if (child !== undefined) {
+ yield* settlePendingOpenCodeRequests(
+ context,
+ child.sessionId,
+ "OpenCode child session was deleted.",
+ );
+ yield* emitChildTaskCompleted(
+ context,
+ child,
+ event,
+ "stopped",
+ "OpenCode child session was deleted.",
+ );
+ yield* completeRootTurnIfSettled(context, event);
}
break;
}
@@ -849,9 +1512,20 @@ export function makeOpenCodeAdapter(
if (part.messageID !== event.properties.info.id) {
continue;
}
- yield* emitAssistantTextDelta(context, part, turnId, event);
+ // Child narration stays out of the parent transcript — it would
+ // interleave subagent prose into the chat. Their results reach
+ // the UI via task.* lifecycle rows instead (mirrors Claude).
+ if (child === undefined) {
+ yield* emitAssistantTextDelta(context, part, turnId, payloadSessionId, event);
+ }
}
}
+ if (child !== undefined && !child.terminal) {
+ yield* emitChildTaskProgress(context, child, event, {
+ summary:
+ event.properties.info.role === "assistant" ? "Generating response" : "Working",
+ });
+ }
break;
}
@@ -869,6 +1543,11 @@ export function makeOpenCodeAdapter(
if (role !== "assistant") {
break;
}
+ // Subagent narration is not streamed into the parent transcript;
+ // task.progress already reports child activity.
+ if (child !== undefined) {
+ break;
+ }
const streamKind = resolveTextStreamKind(existingPart);
const delta = event.properties.delta;
if (delta.length === 0) {
@@ -890,7 +1569,7 @@ export function makeOpenCodeAdapter(
});
}
yield* emit({
- ...(yield* buildEventBase({
+ ...(yield* buildContextEventBase(context, payloadSessionId, {
threadId: context.session.threadId,
turnId,
itemId: event.properties.partID,
@@ -902,6 +1581,11 @@ export function makeOpenCodeAdapter(
delta: deltaToEmit,
},
});
+ if (child !== undefined && !child.terminal) {
+ yield* emitChildTaskProgress(context, child, event, {
+ summary: "Generating response",
+ });
+ }
break;
}
@@ -910,8 +1594,8 @@ export function makeOpenCodeAdapter(
context.partById.set(part.id, part);
const messageRole = messageRoleForPart(context, part);
- if (messageRole === "assistant") {
- yield* emitAssistantTextDelta(context, part, turnId, event);
+ if (messageRole === "assistant" && child === undefined) {
+ yield* emitAssistantTextDelta(context, part, turnId, payloadSessionId, event);
}
if (part.type === "tool") {
@@ -934,7 +1618,7 @@ export function makeOpenCodeAdapter(
},
};
const runtimeEvent: ProviderRuntimeEvent = {
- ...(yield* buildEventBase({
+ ...(yield* buildContextEventBase(context, payloadSessionId, {
threadId: context.session.threadId,
turnId,
itemId: part.callID,
@@ -951,14 +1635,41 @@ export function makeOpenCodeAdapter(
};
appendTurnItem(context, turnId, part);
yield* emit(runtimeEvent);
+ if (child !== undefined && !child.terminal) {
+ yield* emitChildTaskProgress(context, child, event, {
+ summary:
+ part.state.status === "running"
+ ? (part.state.title ?? `Running ${part.tool}`)
+ : part.state.status === "completed"
+ ? `Completed ${part.tool}`
+ : part.state.status === "error"
+ ? `Failed ${part.tool}`
+ : `Working ${part.tool}`,
+ lastToolName: part.tool,
+ });
+ }
}
break;
}
case "permission.asked": {
context.pendingPermissions.set(event.properties.id, event.properties);
+ // The approval surfaces below regardless of child lifecycle; do not
+ // resurrect a child that already emitted task.completed — reopening
+ // it without a fresh task.started leaves settlement state ambiguous.
+ if (child !== undefined) {
+ if (!child.terminal) {
+ child.active = true;
+ }
+ } else {
+ context.rootStatus = "busy";
+ yield* updateProviderSession(context, {
+ status: "running",
+ activeTurnId: turnId,
+ });
+ }
yield* emit({
- ...(yield* buildEventBase({
+ ...(yield* buildContextEventBase(context, payloadSessionId, {
threadId: context.session.threadId,
turnId,
requestId: event.properties.id,
@@ -967,20 +1678,18 @@ export function makeOpenCodeAdapter(
type: "request.opened",
payload: {
requestType: mapPermissionToRequestType(event.properties.permission),
- detail:
- event.properties.patterns.length > 0
- ? event.properties.patterns.join("\n")
- : event.properties.permission,
- args: event.properties.metadata,
+ detail: openCodePermissionDetail(event.properties),
+ args: openCodePermissionArgs(event.properties),
},
});
break;
}
case "permission.replied": {
+ const request = context.pendingPermissions.get(event.properties.requestID);
context.pendingPermissions.delete(event.properties.requestID);
yield* emit({
- ...(yield* buildEventBase({
+ ...(yield* buildContextEventBase(context, payloadSessionId, {
threadId: context.session.threadId,
turnId,
requestId: event.properties.requestID,
@@ -988,17 +1697,33 @@ export function makeOpenCodeAdapter(
})),
type: "request.resolved",
payload: {
- requestType: "unknown",
+ requestType: mapPermissionToRequestType(request?.permission ?? "unknown"),
decision: mapPermissionDecision(event.properties.reply),
},
});
+ if (child !== undefined && !child.terminal) {
+ yield* emitChildTaskProgress(context, child, event, {
+ summary: "Permission response received",
+ });
+ }
+ // The root may have already reported idle while this request was
+ // pending; resolving the last blocker must let the turn finish even
+ // if no further provider event arrives.
+ yield* completeRootTurnIfSettled(context, event);
break;
}
case "question.asked": {
context.pendingQuestions.set(event.properties.id, event.properties);
+ if (child === undefined) {
+ context.rootStatus = "busy";
+ yield* updateProviderSession(context, {
+ status: "running",
+ activeTurnId: turnId,
+ });
+ }
yield* emit({
- ...(yield* buildEventBase({
+ ...(yield* buildContextEventBase(context, payloadSessionId, {
threadId: context.session.threadId,
turnId,
requestId: event.properties.id,
@@ -1022,7 +1747,7 @@ export function makeOpenCodeAdapter(
]),
);
yield* emit({
- ...(yield* buildEventBase({
+ ...(yield* buildContextEventBase(context, payloadSessionId, {
threadId: context.session.threadId,
turnId,
requestId: event.properties.requestID,
@@ -1031,13 +1756,16 @@ export function makeOpenCodeAdapter(
type: "user-input.resolved",
payload: { answers },
});
+ // Mirror the permission path: the root may have idled while the
+ // question was pending.
+ yield* completeRootTurnIfSettled(context, event);
break;
}
case "question.rejected": {
context.pendingQuestions.delete(event.properties.requestID);
yield* emit({
- ...(yield* buildEventBase({
+ ...(yield* buildContextEventBase(context, payloadSessionId, {
threadId: context.session.threadId,
turnId,
requestId: event.properties.requestID,
@@ -1046,11 +1774,23 @@ export function makeOpenCodeAdapter(
type: "user-input.resolved",
payload: { answers: {} },
});
+ yield* completeRootTurnIfSettled(context, event);
break;
}
+ case "session.idle":
+ yield* handleOpenCodeSessionIdle(context, child, event);
+ break;
+
case "session.status": {
if (event.properties.status.type === "busy") {
+ if (child !== undefined && !child.terminal) {
+ child.active = true;
+ yield* emitChildTaskProgress(context, child, event, { summary: "Working" });
+ }
+ if (child === undefined) {
+ context.rootStatus = "busy";
+ }
yield* updateProviderSession(context, {
status: "running",
activeTurnId: turnId,
@@ -1059,7 +1799,7 @@ export function makeOpenCodeAdapter(
if (event.properties.status.type === "retry") {
yield* emit({
- ...(yield* buildEventBase({
+ ...(yield* buildContextEventBase(context, payloadSessionId, {
threadId: context.session.threadId,
turnId,
raw: event,
@@ -1070,30 +1810,54 @@ export function makeOpenCodeAdapter(
detail: event.properties.status,
},
});
+ if (child !== undefined && !child.terminal) {
+ yield* emitChildTaskProgress(context, child, event, {
+ summary: event.properties.status.message,
+ });
+ }
break;
}
- if (event.properties.status.type === "idle" && turnId) {
- context.activeTurnId = undefined;
- yield* updateProviderSession(context, { status: "ready" }, { clearActiveTurnId: true });
+ if (event.properties.status.type === "idle") {
+ yield* handleOpenCodeSessionIdle(context, child, event);
+ }
+ break;
+ }
+
+ case "session.error": {
+ const message = sessionErrorMessage(event.properties.error);
+ if (child !== undefined) {
+ child.failure = message;
+ child.active = false;
+ yield* settlePendingOpenCodeRequests(context, child.sessionId, message);
+ yield* emitChildTaskCompleted(context, child, event, "failed", message);
yield* emit({
- ...(yield* buildEventBase({
+ ...(yield* buildContextEventBase(context, payloadSessionId, {
threadId: context.session.threadId,
turnId,
raw: event,
})),
- type: "turn.completed",
+ type: "runtime.error",
payload: {
- state: "completed",
+ message,
+ class: "provider_error",
+ detail: event.properties.error,
},
});
+ yield* completeRootTurnIfSettled(context, event);
+ break;
}
- break;
- }
- case "session.error": {
- const message = sessionErrorMessage(event.properties.error);
const activeTurnId = context.activeTurnId;
+ context.rootStatus = "idle";
+ yield* abortOpenCodeSessions(context);
+ yield* settlePendingOpenCodeRequests(context, undefined, message);
+ for (const childSession of context.childSessions.values()) {
+ if (childSession.active && !childSession.terminal) {
+ childSession.failure = message;
+ yield* emitChildTaskCompleted(context, childSession, event, "failed", message);
+ }
+ }
context.activeTurnId = undefined;
yield* updateProviderSession(
context,
@@ -1105,7 +1869,7 @@ export function makeOpenCodeAdapter(
);
if (activeTurnId) {
yield* emit({
- ...(yield* buildEventBase({
+ ...(yield* buildContextEventBase(context, payloadSessionId, {
threadId: context.session.threadId,
turnId: activeTurnId,
raw: event,
@@ -1118,7 +1882,7 @@ export function makeOpenCodeAdapter(
});
}
yield* emit({
- ...(yield* buildEventBase({
+ ...(yield* buildContextEventBase(context, payloadSessionId, {
threadId: context.session.threadId,
raw: event,
})),
@@ -1210,7 +1974,7 @@ export function makeOpenCodeAdapter(
const resumeSessionId = parseOpenCodeResume(input.resumeCursor)?.sessionId;
const existing = sessions.get(input.threadId);
if (existing) {
- yield* stopOpenCodeContext(existing);
+ yield* stopOpenCodeContext(existing, openCodeContextCleanup);
sessions.delete(input.threadId);
}
@@ -1394,6 +2158,8 @@ export function makeOpenCodeAdapter(
openCodeSessionId: started.openCodeSession.id,
pendingPermissions: new Map(),
pendingQuestions: new Map(),
+ childSessions: new Map(),
+ ignoredSessionIds: new Set(),
partById: new Map(),
emittedTextByPartId: new Map(),
messageRoleById: new Map(),
@@ -1402,6 +2168,7 @@ export function makeOpenCodeAdapter(
activeTurnId: undefined,
activeAgent: undefined,
activeVariant: undefined,
+ rootStatus: "unknown",
stopped: yield* Ref.make(false),
sessionScope: started.sessionScope,
};
@@ -1476,6 +2243,7 @@ export function makeOpenCodeAdapter(
const variant = getModelSelectionStringOptionValue(modelSelection, "variant");
context.activeTurnId = turnId;
+ context.rootStatus = "busy";
context.activeAgent = agent ?? (input.interactionMode === "plan" ? "plan" : undefined);
context.activeVariant = variant;
yield* updateProviderSession(
@@ -1519,6 +2287,7 @@ export function makeOpenCodeAdapter(
? Effect.void
: Effect.gen(function* () {
context.activeTurnId = undefined;
+ context.rootStatus = "idle";
context.activeAgent = undefined;
context.activeVariant = undefined;
yield* updateProviderSession(
@@ -1558,14 +2327,26 @@ export function makeOpenCodeAdapter(
const interruptTurn: OpenCodeAdapterShape["interruptTurn"] = Effect.fn("interruptTurn")(
function* (threadId, turnId) {
const context = yield* ensureSessionContext(sessions, threadId);
- yield* runOpenCodeSdk("session.abort", () =>
- context.client.session.abort({ sessionID: context.openCodeSessionId }),
- ).pipe(Effect.mapError(toRequestError));
- if (turnId ?? context.activeTurnId) {
+ const activeTurnId = turnId ?? context.activeTurnId;
+ if (openCodeContextCleanup.abortSessions) {
+ yield* openCodeContextCleanup.abortSessions(context);
+ }
+ if (openCodeContextCleanup.settlePendingRequests) {
+ yield* openCodeContextCleanup.settlePendingRequests(context);
+ }
+ if (openCodeContextCleanup.completeChildTasks) {
+ yield* openCodeContextCleanup.completeChildTasks(context);
+ }
+ context.rootStatus = "idle";
+ context.activeTurnId = undefined;
+ context.activeAgent = undefined;
+ context.activeVariant = undefined;
+ yield* updateProviderSession(context, { status: "ready" }, { clearActiveTurnId: true });
+ if (activeTurnId) {
yield* emit({
- ...(yield* buildEventBase({
+ ...(yield* buildContextEventBase(context, context.openCodeSessionId, {
threadId,
- turnId: turnId ?? context.activeTurnId,
+ turnId: activeTurnId,
})),
type: "turn.aborted",
payload: {
@@ -1580,7 +2361,8 @@ export function makeOpenCodeAdapter(
"respondToRequest",
)(function* (threadId, requestId, decision) {
const context = yield* ensureSessionContext(sessions, threadId);
- if (!context.pendingPermissions.has(requestId)) {
+ const request = context.pendingPermissions.get(requestId);
+ if (!request) {
return yield* new ProviderAdapterRequestError({
provider: PROVIDER,
method: "permission.reply",
@@ -1591,6 +2373,7 @@ export function makeOpenCodeAdapter(
yield* runOpenCodeSdk("permission.reply", () =>
context.client.permission.reply({
requestID: requestId,
+ directory: directoryForOpenCodeSession(context, request.sessionID),
reply: toOpenCodePermissionReply(decision),
}),
).pipe(Effect.mapError(toRequestError));
@@ -1612,6 +2395,7 @@ export function makeOpenCodeAdapter(
yield* runOpenCodeSdk("question.reply", () =>
context.client.question.reply({
requestID: requestId,
+ directory: directoryForOpenCodeSession(context, request.sessionID),
answers: toOpenCodeQuestionAnswers(request, answers),
}),
).pipe(Effect.mapError(toRequestError));
@@ -1626,7 +2410,7 @@ export function makeOpenCodeAdapter(
threadId,
});
}
- const stopped = yield* stopOpenCodeContext(context);
+ const stopped = yield* stopOpenCodeContext(context, openCodeContextCleanup);
sessions.delete(threadId);
if (!stopped) {
return;
@@ -1710,7 +2494,7 @@ export function makeOpenCodeAdapter(
// interrupt the sibling fibers. Same pattern as the layer finalizer.
yield* Effect.forEach(
contexts,
- (context) => Effect.ignoreCause(stopOpenCodeContext(context)),
+ (context) => Effect.ignoreCause(stopOpenCodeContext(context, openCodeContextCleanup)),
{ concurrency: "unbounded", discard: true },
);
});
diff --git a/apps/web/src/components/chat/ComposerPendingApprovalPanel.test.tsx b/apps/web/src/components/chat/ComposerPendingApprovalPanel.test.tsx
index 5e82298d50f0..64e6ce00a721 100644
--- a/apps/web/src/components/chat/ComposerPendingApprovalPanel.test.tsx
+++ b/apps/web/src/components/chat/ComposerPendingApprovalPanel.test.tsx
@@ -50,4 +50,22 @@ describe("ComposerPendingApprovalPanel", () => {
expect(markup).toContain("File read approval");
});
+
+ it("renders generic permission details", () => {
+ const markup = renderToStaticMarkup(
+ ,
+ );
+
+ expect(markup).toContain("Permission approval");
+ expect(markup).toContain('aria-label="Permission details"');
+ expect(markup).toContain("external_directory");
+ });
});
diff --git a/apps/web/src/components/chat/ComposerPendingApprovalPanel.tsx b/apps/web/src/components/chat/ComposerPendingApprovalPanel.tsx
index d73f0f16b28f..0a8331853c11 100644
--- a/apps/web/src/components/chat/ComposerPendingApprovalPanel.tsx
+++ b/apps/web/src/components/chat/ComposerPendingApprovalPanel.tsx
@@ -18,13 +18,17 @@ export const ComposerPendingApprovalPanel = memo(function ComposerPendingApprova
? "Command approval"
: approval.requestKind === "file-read"
? "File read approval"
- : "File change approval";
+ : approval.requestKind === "file-change"
+ ? "File change approval"
+ : "Permission approval";
const detailAriaLabel =
approval.requestKind === "command"
? "Command"
: approval.requestKind === "file-read"
? "File to read"
- : "File change";
+ : approval.requestKind === "file-change"
+ ? "File change"
+ : "Permission details";
return (
{
]);
});
+ it("keeps unknown provider permissions visible as generic approvals", () => {
+ const activities: OrchestrationThreadActivity[] = [
+ makeActivity({
+ id: "approval-open-external-directory",
+ kind: "approval.requested",
+ summary: "Approval requested",
+ tone: "approval",
+ payload: {
+ requestId: "req-external-directory",
+ requestType: "unknown",
+ detail: "Permission: external_directory\n\nPatterns:\n/another/project/*",
+ args: {
+ permission: "external_directory",
+ sessionID: "ses_child",
+ },
+ },
+ }),
+ ];
+
+ expect(derivePendingApprovals(activities)).toEqual([
+ {
+ requestId: "req-external-directory",
+ requestKind: "unknown",
+ createdAt: "2026-02-23T00:00:00.000Z",
+ detail: "Permission: external_directory\n\nPatterns:\n/another/project/*",
+ },
+ ]);
+ });
+
it("clears stale pending approvals when provider reports unknown pending request", () => {
const activities: OrchestrationThreadActivity[] = [
makeActivity({
diff --git a/apps/web/src/session-logic.ts b/apps/web/src/session-logic.ts
index 4824258422fb..06c12a85a241 100644
--- a/apps/web/src/session-logic.ts
+++ b/apps/web/src/session-logic.ts
@@ -109,7 +109,7 @@ interface DerivedWorkLogEntry extends WorkLogEntry {
export interface PendingApproval {
requestId: ApprovalRequestId;
- requestKind: "command" | "file-read" | "file-change";
+ requestKind: "command" | "file-read" | "file-change" | "unknown";
createdAt: string;
detail?: string;
}
@@ -378,6 +378,8 @@ function requestKindFromRequestType(requestType: unknown): PendingApproval["requ
case "file_change_approval":
case "apply_patch_approval":
return "file-change";
+ case "unknown":
+ return "unknown";
default:
return null;
}
@@ -418,7 +420,8 @@ export function derivePendingApprovals(
payload &&
(payload.requestKind === "command" ||
payload.requestKind === "file-read" ||
- payload.requestKind === "file-change")
+ payload.requestKind === "file-change" ||
+ payload.requestKind === "unknown")
? payload.requestKind
: payload
? requestKindFromRequestType(payload.requestType)
@@ -1674,7 +1677,8 @@ function extractWorkLogRequestKind(
if (
payload?.requestKind === "command" ||
payload?.requestKind === "file-read" ||
- payload?.requestKind === "file-change"
+ payload?.requestKind === "file-change" ||
+ payload?.requestKind === "unknown"
) {
return payload.requestKind;
}
diff --git a/packages/contracts/src/providerRuntime.ts b/packages/contracts/src/providerRuntime.ts
index bd525e6542e2..cdfd00d0b3d8 100644
--- a/packages/contracts/src/providerRuntime.ts
+++ b/packages/contracts/src/providerRuntime.ts
@@ -43,6 +43,10 @@ const ProviderRequestId = TrimmedNonEmptyStringSchema;
export type ProviderRequestId = typeof ProviderRequestId.Type;
const ProviderRefs = Schema.Struct({
+ /** Provider-native session identifier, including nested child sessions. */
+ providerSessionId: Schema.optional(TrimmedNonEmptyStringSchema),
+ /** Provider-native parent session identifier for nested child sessions. */
+ providerParentSessionId: Schema.optional(TrimmedNonEmptyStringSchema),
providerTurnId: Schema.optional(TrimmedNonEmptyStringSchema),
providerItemId: Schema.optional(ProviderItemId),
providerRequestId: Schema.optional(ProviderRequestId),