/** * The v2 session read seam, shaped like the v1 client the collect path expects (#8). * * `tm_join` collects a child in two ways: it looks the child up in its own registry * (children THIS plugin process created — and since `babfcb8` also the host's `subagent` * children), and otherwise it asks the host. That ask was written against v1's SDK client * (`client.session.get({path:{id}})`), and on a live host it never once resolved. * * Why it failed, read out of 2.0.18 rather than guessed (both quotes are from the embedded * JS): the plugin-facing wrapper is a schema decode plus a single-object call — * * te = (schema, fn) => (input) => decode(schema, input ?? {}) → fn(decoded) * get: te(N["session.get"], a.session.get) * * and the host's own call sites are flat: * * await e.session.get({ sessionID: n }) * await e.message.list({ sessionID: n, limit: 200, cursor: r }) * * So our adapter had TWO bugs: the key (`path.id` / `id` instead of `sessionID`) and the * arity (every shape was passed as a one-element ARRAY, which no `te`-wrapped method * accepts). A decode failure looks exactly like "the host has no such method", which is * how this stayed wrong for a whole release cycle. * * What this surface does NOT give, stated rather than discovered later: * · no `children` method, so adoption from the host's session tree is still impossible on * v2 — only an explicitly named id can be claimed, which is what the lead's prompt says. * · no `message`/`messages` domain on the plugin ctx (the host's internal `message.list` * is the SDK client, not this surface). `context` is the only candidate that could * carry a child's reply, so it is tried, and its outcome — including the NAMES of the * keys it returned — is recorded. Key names are safe; a message body is not written to * the trajectory by this file. * · the HTTP API does expose `GET /api/session/{id}/message` (136 distinct routes were * enumerated from a scanned window of the binary, and none of them is a todo list — * which settles the `tm_ledger` question: there is no host todo resource to read), but * reaching it needs the basic-auth password from * `~/.config/opencode/service.json`. A plugin reading the user's credential file to * call back into the host is a new trust boundary, so it is NOT done here; it is * recorded as the option with its cost attached. See * `docs/research/host-http-api.md`. */ export interface V2SessionReaderReport { /** calls made through this adapter */ attempted: number; /** calls that produced an object with a parent id */ resolved: number; /** which argument shape the host answered — `sessionID` is the measured one */ usedShape?: string; /** last error text, unwrapped from the host's own error envelope */ lastError?: string; /** shapes tried and failed on the last call */ failedShapes: string[]; /** `ctx.session.context` was reached at all */ contextTried: number; contextOk: number; contextError?: string; /** the KEY NAMES the context call returned (names only, never values) */ contextKeys: string[]; /** `ctx.session.interrupt` calls made by `tm_join { cancel: true }` (#33) */ interruptTried: number; /** the host confirmed an active execution was interrupted (`interrupted: true`) */ interruptConfirmed: number; /** the host answered `interrupted: false` — the documented idle no-op, i.e. NOT a stop */ interruptRefused: number; /** the call succeeded and carried no `interrupted` boolean at all */ interruptUnknown: number; interruptError?: string; /** the KEY NAMES the interrupt call returned (names only, never values) */ interruptKeys: string[]; /** the SHAPE of the last list the seam answered with — names and counts only, and the * reason a `child_body_missing` row can be checked later (`contextShapeEvidence`). */ contextShape?: string; /** #steer — `tm_join { steer }` calls. The three outcomes are counted separately because * the reply may only say 已受理 for the first one: the host admitting the input is an * observation (it named the inbox item), the host answering without an id is not. */ promptTried: number; /** the answer carried a `^msg_` inbox id — the only evidence of acceptance */ promptConfirmed: number; /** the call worked and no inbox id came back, or the host refused */ promptUnconfirmed: number; promptError?: string; /** the KEY NAMES the prompt/synthetic answer returned (names only, never values) */ promptKeys: string[]; /** which seam carried the steering input: `session.prompt` or `session.synthetic` */ steerSeamUsed?: string; /** `session.inbox.list` / `session.inbox.cancel` calls (unread / unsend) */ inboxListTried: number; inboxListOk: number; inboxCancelTried: number; /** the host named the item it removed */ inboxCancelConfirmed: number; /** the host answered and named nothing — the documented no-op for an unavailable item */ inboxCancelNoop: number; inboxError?: string; /** which SPELLING of each inbox op answered (names only) */ inboxSpellings: string[]; } export interface V2SessionReader { /** a `{session:{get,messages}}` object safe to hand to code written for the v1 client */ client: unknown; report: V2SessionReaderReport; } /** Turn the host's context items into the `{info,parts}` shape the collect path already * understands — HERE, at the seam that knows the shape, so nothing downstream has to guess. * Three shapes are in the wild and all three are measurements, not version numbers: * · 2.0.18 `[L]`: a FLAT item per message, `{id, time:{created}, text, type}`. * · 2.0.20 `[L][D]`: a MESSAGE whose body is `content: (Text|Reasoning|Tool)[]` and whose * kind is on `role` — the name-level probe recorded `{keys:["content","id","metadata", * "role"]}`. The previous version of this function passed that item through untouched * ("already message-shaped"), `lastAssistantMessage` found no `parts` and no `text` on * it, and the seam answered while the body was dropped AGAIN — which is why 正文来源= * never printed on 2.0.20 (A1). * · v1's `[{info,parts[]}]`: passed through untouched, so a host that changes back needs * no change here. * Only the bag is renamed and the envelope added: the elements are already v1 part shapes * (`{type:"text", text}`), and reasoning/tool parts stay exactly as the host wrote them. * An empty `content` normalises to empty parts — this function MOVES text, it never * manufactures any. */ export declare function normaliseContextMessages(list: unknown): unknown; /** WHY a body came back empty, in names and counts only — never a value, a path, or a * snippet of text (the R6 口径 binds a diagnostic). tm_join writes this onto the * `child_body_missing` trajectory line so the three conclusions stay distinguishable: * a real body (`child_body`), a seam that answered with nothing readable in it * (`items=2 keys=[content+id+metadata+role] content=1 kinds=[tool] text=0`), and a seam * that did not answer at all (`no-context-method`). Without it, "we read it" and "we could * not read it" print the same row — goal #6's failure shape. */ export declare function contextShapeEvidence(list: unknown): string; export declare function createV2SessionReader(ctx: unknown): V2SessionReader; //# sourceMappingURL=v2-session-client.d.ts.map