Merge origin/master (plan line #590) into web-permission-sandbox-merge-master
Shared-surface conflicts resolve as unions: the fixture serves all five projection keys (title/todos/permissions/plan/goal) with the /permission and /plan command mirrors side by side, the connection specs assert the five-key baseline and the shifted approval/question replay indices, and the cli roster/deps, tsconfig aggregate, and README allowlist carry both lines' rows. Plan-side content lands verbatim from master. One end-state consolidation both branches half-did: with questions (ui-question) and approvals (ApprovalPanel) each owning a composer takeover, PendingCard retires outright — ChatView renders no pending placeholder, the card component and its specs go, and both README halves state the takeover-only contract.
This commit is contained in:
@@ -147,13 +147,24 @@ function subscribeSession(queue: FrameQueue<RpcRequest<MuxFrame>>, session: Sess
|
||||
queue.push(frame({ type: 'session/subscribed', sessionId: session.id, lastSeq: session.seq - 1 }))
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the session's conversation has started: no turn has run yet (a
|
||||
* turn is one model-loop execution). Standalone plugin events — command
|
||||
* lifecycle records, plan/mode, titles, goals — never open a turn, so
|
||||
* running `/plan` or `/goal` on a fresh session keeps it blank
|
||||
* (list-hidden, reusable).
|
||||
*/
|
||||
function sessionBlank(session: Session): boolean {
|
||||
return !session.events.some(event => event.type === 'turn/start')
|
||||
}
|
||||
|
||||
/** SessionSummary projection for attached (in-memory) sessions. */
|
||||
function summarize(session: Session, running: boolean): SessionSummary {
|
||||
return {
|
||||
sessionId: session.id,
|
||||
updatedAt: session.events.at(-1)?.time ?? session.header.createdAt,
|
||||
running,
|
||||
blank: session.events.length === 0,
|
||||
blank: sessionBlank(session),
|
||||
...session.header.parentSession === undefined ? {} : { parentSessionId: session.header.parentSession },
|
||||
...session.header.cwd === undefined ? {} : { cwd: session.header.cwd },
|
||||
}
|
||||
@@ -178,8 +189,9 @@ async function summarizeCold(persistence: SessionPersistence, meta: SessionHeade
|
||||
sessionId: meta.id,
|
||||
updatedAt,
|
||||
running: false,
|
||||
// Lazy persistence keeps never-appended sessions out of list(): a cold
|
||||
// session necessarily has events, so blank is constantly false here.
|
||||
// Lazy persistence keeps never-appended sessions out of list(); reading
|
||||
// a cold log to check for turns would defeat the index read, so a listed
|
||||
// cold session is served as not-blank (its log holds its conversation).
|
||||
blank: false,
|
||||
...meta.parentSession === undefined ? {} : { parentSessionId: meta.parentSession },
|
||||
/* v8 ignore next -- the empty arm needs a cwd-less meta, but list()
|
||||
@@ -1435,8 +1447,8 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro
|
||||
type: 'host/session-added',
|
||||
sessionId: session.id,
|
||||
// Derived at frame time like summarize(); a just-created session
|
||||
// has no events yet, so this is constantly true in practice.
|
||||
blank: session.events.length === 0,
|
||||
// has run no turn yet, so this is constantly true in practice.
|
||||
blank: sessionBlank(session),
|
||||
...session.header.parentSession === undefined ? {} : { parentSessionId: session.header.parentSession },
|
||||
// cwd rides the frame so the client list needs no refresh to group the new session.
|
||||
...session.header.cwd === undefined ? {} : { cwd: session.header.cwd },
|
||||
|
||||
Reference in New Issue
Block a user