Merge remote-tracking branch 'origin/master' into worktree/session-reference
# Conflicts: # docs/capability-seams.md # docs/config-catalog.md # docs/cordis-catalog/services.md # docs/module-graph.md # examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl # packages/cordis/tool-cordis/src/api-catalog.ts # packages/ui/acp/README.md # packages/ui/acp/package.json # packages/ui/acp/tsconfig.json # packages/ui/tui/package.json # packages/ui/tui/src/index.ts # packages/ui/tui/tests/tui.spec.ts # packages/ui/tui/tsconfig.json # pnpm-lock.yaml # python/sdk-runtime/package.json # scripts/gen-doc-graphs.ts # scripts/type-equiv.manifest.json
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
|
||||
## Public API
|
||||
|
||||
- `listCandidates(agent, query?, limit?)` lists sessions other than `agent.id`, filters case-insensitively by id or cwd, and ranks same-cwd, cwd-less, then other-cwd records while preserving `listSessions()` creation order within each group. It searches no title or message body.
|
||||
- `listCandidates(agent, query?, limit?)` lists sessions other than `agent.id`, filters case-insensitively by id or cwd, and ranks same-cwd, cwd-less, then other-cwd records while preserving `listSessions()` creation order within each group. Each selected candidate uses its latest log-backed title as the mention label and falls back to the session id; titles and message bodies are not searched.
|
||||
- `prepare(agent, content, references, signal?)` preserves first-mention order, deduplicates ids, rejects self-reference and more than the configured distinct-source limit, reads every source in parallel, and returns detached content plus zero or one aggregated `HookContext`. Any invalid reference, failed read, cancellation, or budget failure rejects before the host calls `send()` or `steer()`.
|
||||
- `encodeSessionReferenceUri()` and `decodeSessionReferenceUri()` implement `dsh-session:<base64url(JSON.stringify(sessionId))>` so every JavaScript string id round-trips exactly. `formatSessionReferenceMention()` emits `@[label](uri)`, and `parseSessionReferenceText()` replaces Markdown mentions or bare canonical URIs with readable `@label` text while returning structured references. Explicit Markdown mentions reject every malformed URI; bare text is considered a reference only when a non-empty base64url-shaped payload follows the scheme, and a matching noncanonical candidate still fails. Empty or punctuation-only scheme mentions remain ordinary discussion text.
|
||||
|
||||
@@ -42,7 +42,7 @@ Snapshot context is append-only at the target message boundary and preserves ear
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
- **No full-text discovery** — candidates use session id and cwd only. SQLite FTS or title metadata may replace discovery later without changing URI, snapshot, or persistence contracts.
|
||||
- **No title or full-text discovery** — candidates filter by session id and cwd only, although selected rows display the latest title. SQLite FTS may replace discovery later without changing URI, snapshot, or persistence contracts.
|
||||
- **Trusted caller boundary** — the service assumes its host is authorized to read every session exposed by `ctx.sessionQuery`; it is not a model-facing search tool.
|
||||
- **Text projection only** — non-text user and assistant blocks are not propagated across sessions.
|
||||
- **No live link** — references are snapshots, not forks, resumes, subscriptions, or source-session mutations.
|
||||
|
||||
@@ -100,12 +100,12 @@ export class SessionReferenceService extends Service {
|
||||
}
|
||||
|
||||
/**
|
||||
* List metadata-only reference candidates, ranked by working-directory affinity.
|
||||
* List reference candidates, ranked by working-directory affinity.
|
||||
* @param agent - target agent; self is excluded and its cwd drives ranking.
|
||||
* @param query - optional case-insensitive session-id/cwd substring.
|
||||
* @param limit - optional positive result cap.
|
||||
* @param signal - optional cancellation boundary for host autocomplete teardown.
|
||||
* @returns candidate records in stable source creation order within each rank.
|
||||
* @returns candidates labeled by latest title or, when absent, session id.
|
||||
*/
|
||||
async listCandidates(
|
||||
agent: Agent,
|
||||
@@ -130,9 +130,13 @@ export class SessionReferenceService extends Service {
|
||||
.sort((a, b) => candidateRank(a.record.header.cwd, targetCwd) - candidateRank(b.record.header.cwd, targetCwd)
|
||||
|| a.index - b.index)
|
||||
.slice(0, limit)
|
||||
return records.map(({ record }) => ({
|
||||
const titles = await settleWithCancellation(
|
||||
Promise.all(records.map(({ record }) => this.ctx.sessionQuery.readTitle(record.header.id))),
|
||||
signal,
|
||||
)
|
||||
return records.map(({ record }, index) => ({
|
||||
sessionId: record.header.id,
|
||||
label: record.header.id,
|
||||
label: titles[index]?.title ?? record.header.id,
|
||||
...record.header.cwd === undefined ? {} : { cwd: record.header.cwd },
|
||||
createdAt: record.header.createdAt,
|
||||
}))
|
||||
|
||||
@@ -16,7 +16,7 @@ export interface SessionReferenceInput {
|
||||
export interface SessionReferenceCandidate {
|
||||
/** Opaque source session identity. */
|
||||
sessionId: SessionId
|
||||
/** Default display label. */
|
||||
/** Latest log-backed title, falling back to the opaque session id. */
|
||||
label: string
|
||||
/** Source session working directory, when recorded. */
|
||||
cwd?: string
|
||||
|
||||
@@ -177,10 +177,15 @@ describe('session reference discovery and preparation', () => {
|
||||
ctx.sessions.create(SessionId('other'), { meta: { cwd: '/else', createdAt: 40 } })
|
||||
ctx.sessions.create(SessionId('none'), { meta: { createdAt: 30 } })
|
||||
ctx.sessions.create(SessionId('same'), { meta: { cwd: '/same', createdAt: 20 } })
|
||||
ctx.sessions.create(SessionId('same-later'), { meta: { cwd: '/same', createdAt: 25 } })
|
||||
const sameLater = ctx.sessions.create(SessionId('same-later'), { meta: { cwd: '/same', createdAt: 25 } })
|
||||
sameLater.append('session/title', {
|
||||
title: 'Latest title',
|
||||
messageSeqs: [],
|
||||
source: { kind: 'fallback' },
|
||||
})
|
||||
|
||||
await expect(ctx.sessionReferences.listCandidates(fakeAgent(target))).resolves.toEqual([
|
||||
{ sessionId: SessionId('same-later'), label: 'same-later', cwd: '/same', createdAt: 25 },
|
||||
{ sessionId: SessionId('same-later'), label: 'Latest title', cwd: '/same', createdAt: 25 },
|
||||
{ sessionId: SessionId('same'), label: 'same', cwd: '/same', createdAt: 20 },
|
||||
{ sessionId: SessionId('none'), label: 'none', createdAt: 30 },
|
||||
{ sessionId: SessionId('other'), label: 'other', cwd: '/else', createdAt: 40 },
|
||||
|
||||
Reference in New Issue
Block a user