Skip to content
5 changes: 4 additions & 1 deletion documentor/FORK.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ what a rebase meets.
| `packages/sdk/js/src/v2/gen/*` | `session.create` takes `id`; `session.prompt` and `session.promptAsync` take `author` and `answerable`, `UserMessage` declares both, and `Config.experimental` declares `openTelemetryChatContent` | Generated by `packages/sdk/js/script/build.ts` from the server's OpenAPI |
| `packages/opencode/src/acp/event.ts` | `agent_message_chunk`, `agent_thought_chunk`, `tool_call` and `tool_call_update`, live and replayed, carry `_meta.opencode = { messageId, parentId }`: the assistant message and the user message it answers. The subscription caches each assistant message's `parentID` from `message.updated` for the sessions it tracks. On a miss it fetches the message once, shared by concurrent updates; a failed fetch is remembered until the session goes idle, and meanwhile the updates carry `parentId: null`. A session's cache is forgotten when it goes idle, and the whole cache when the event stream disconnects | DocuMentor detects from the ACP stream alone, in order, the moment the agent starts answering a message sent while the turn ran |
| `packages/opencode/src/acp/event.ts` | A replayed `user_message_chunk` carries `_meta.opencode = { messageId, parentId: null, author?, answerable?, partMetadata? }` | DocuMentor rebuilds a discussion from the replay with authors and mention metadata |
| `packages/opencode/src/session/prompt.ts` | `Service.title` runs the first-turn title agent over the newest 1–50 real messages of a session on demand, with an optional compare-and-set on the current title; `ensureTitle` and `title` share one `generateTitle` | DocuMentor titles a project discussion after its first message and offers *Suggest a title* on any chat; the first-turn step never runs for a discussion, whose title is set before any turn |
| `packages/opencode/src/server/routes/instance/httpapi/{groups,handlers}/session.ts` | `POST /session/:sessionID/title` | The only channel DocuMentor's web tier has to the title agent |
| `packages/opencode/test/lib/llm-server.ts` | A title request is served by a queued entry whose `match` matches it before the fixed `E2E Title` answer | The on-demand title tests choose the answer |
| Fork cases in `packages/opencode/test/acp/event.test.ts`, `test/acp/service-session.test.ts`, `test/session/prompt.test.ts`, `test/session/message-v2.test.ts`, `test/session/compaction.test.ts`, `test/session/session.test.ts` `test/session/llm.test.ts` and `test/session/fork-batch.test.ts`, and `packages/core/test/event-publish-many.test.ts` | Tests named `documentor: …` | They pin the ACP, session and telemetry rows above; a rebase keeps them |
| `packages/core/src/integration.ts` | An OAuth attempt's credential is stored before the attempt reads as complete, inside one update of the attempts map | The attempt was marked complete first and the credential written after. On SQLite nothing could look in between; on a network database a caller that saw "complete" could list no credential |
| `patches/effect@4.0.0-beta.83.patch` | Adds Effect's own fix for re-entrant interruption, first released in `4.0.0-beta.100` | A fiber that woke another fiber which interrupted it synchronously had its continuation stack corrupted once its cleanup awaited anything. With Postgres that leaked a pooled connection. Drop these hunks when upstream moves to `beta.100` or later |
Expand Down Expand Up @@ -137,7 +140,7 @@ must produce the same `migration` journal;
`documentor/scripts/binary-smoke.sh`.
6. Read `upstream-test-exceptions.md` against the rebased tests. A test that
upstream rewrote may no longer need its exception.
7. Run `bun test test/acp/event.test.ts test/acp/service-session.test.ts test/session/prompt.test.ts test/session/message-v2.test.ts test/session/compaction.test.ts test/session/llm.test.ts -t "documentor:"` in `packages/opencode`. These pin the ACP, session and telemetry changes DocuMentor's queued messages, project discussions and trace privacy depend on; a failure means upstream moved the code they patch.
7. Run `bun test test/acp/event.test.ts test/acp/service-session.test.ts test/session/prompt.test.ts test/session/message-v2.test.ts test/session/compaction.test.ts test/session/llm.test.ts test/server/httpapi-session.test.ts -t "documentor:"` in `packages/opencode`. These pin the ACP, session and telemetry changes DocuMentor's queued messages, project discussions and trace privacy depend on; a failure means upstream moved the code they patch.

A new upstream test that fails only on Postgres is most often one of two
things. Either it reads state the instant a call returns, before a background
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export const SummarizePayload = Schema.Struct({
modelID: ModelV2.ID,
auto: Schema.optional(Schema.Boolean),
})
export const TitlePayload = Schema.Struct(Struct.omit(SessionPrompt.TitleInput.fields, ["sessionID"]))
export const PromptPayload = Schema.Struct(Struct.omit(SessionPrompt.PromptInput.fields, ["sessionID"]))
export const CommandPayload = Schema.Struct(Struct.omit(SessionPrompt.CommandInput.fields, ["sessionID"]))
export const ShellPayload = Schema.Struct(Struct.omit(SessionPrompt.ShellInput.fields, ["sessionID"]))
Expand All @@ -92,6 +93,7 @@ export const SessionPaths = {
share: `${root}/:sessionID/share`,
init: `${root}/:sessionID/init`,
summarize: `${root}/:sessionID/summarize`,
title: `${root}/:sessionID/title`,
prompt: `${root}/:sessionID/message`,
promptAsync: `${root}/:sessionID/prompt_async`,
command: `${root}/:sessionID/command`,
Expand Down Expand Up @@ -313,6 +315,20 @@ export const SessionApi = HttpApi.make("session")
description: "Generate a concise summary of the session using AI compaction to preserve key information.",
}),
),
HttpApiEndpoint.post("title", SessionPaths.title, {
params: { sessionID: SessionID },
query: WorkspaceRoutingQuery,
payload: TitlePayload,
success: described(SessionPrompt.TitleResult, "Generated title"),
error: [HttpApiError.BadRequest, ApiNotFoundError],
}).annotateMerge(
OpenApi.annotations({
identifier: "session.title",
summary: "Title session",
description:
"Generate a title for the session from its newest messages, optionally only while its title still equals ifTitle.",
}),
),
HttpApiEndpoint.post("prompt", SessionPaths.prompt, {
params: { sessionID: SessionID },
query: WorkspaceRoutingQuery,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ import {
RevertPayload,
ShellPayload,
SummarizePayload,
TitlePayload,
UpdatePayload,
} from "../groups/session"
import { PermissionNotFoundError } from "../errors"
import { notFound, PermissionNotFoundError } from "../errors"
import * as SessionError from "./session-errors"

const tryParseJson = (text: string) =>
Expand Down Expand Up @@ -292,6 +293,17 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
return true
})

const title = Effect.fn("SessionHttpApi.title")(function* (ctx: {
params: { sessionID: SessionID }
payload: typeof TitlePayload.Type
}) {
yield* requireSession(ctx.params.sessionID)
return yield* promptSvc.title({ sessionID: ctx.params.sessionID, ...ctx.payload }).pipe(
Effect.catchTag("TitleInputError", () => Effect.fail(new HttpApiError.BadRequest({}))),
Effect.catchTag("NotFoundError", (error) => Effect.fail(notFound(error.message))),
)
})

const prompt = Effect.fn("SessionHttpApi.prompt")(function* (ctx: {
params: { sessionID: SessionID }
payload: typeof PromptPayload.Type
Expand Down Expand Up @@ -428,6 +440,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
.handle("share", share)
.handle("unshare", unshare)
.handle("summarize", summarize)
.handle("title", title)
.handle("prompt", prompt)
.handle("promptAsync", promptAsync)
.handle("command", command)
Expand Down
Loading
Loading