Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions src/services/context/formatters/AgentFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
} from '../types.js';
import { ModeManager } from '../../domain/ModeManager.js';
import { formatObservationTokenDisplay } from '../TokenCalculator.js';
import { formatContextReferenceId } from './id-display.js';

function formatHeaderDateTime(): string {
const now = new Date();
Expand All @@ -28,14 +29,18 @@ export function renderAgentHeader(project: string): string[] {
];
}

export function renderAgentLegend(): string[] {
export function renderAgentLegend(fetchByIdSupported: boolean = true): string[] {
const mode = ModeManager.getInstance().getActiveMode();
const typeLegendItems = mode.observation_types.map(t => `${t.emoji}${t.id}`).join(' ');

const fetchLine = fetchByIdSupported
? `Fetch details: get_observations([IDs]) | Search: mem-search skill`
: `Fetch details: mem-search by title/context (short refs are display-only)`;

return [
`Legend: 🎯session ${typeLegendItems}`,
`Format: ID TIME TYPE TITLE`,
`Fetch details: get_observations([IDs]) | Search: mem-search skill`,
fetchLine,
''
];
}
Expand Down Expand Up @@ -90,13 +95,14 @@ function compactTime(time: string): string {
export function renderAgentTableRow(
obs: Observation,
timeDisplay: string,
_config: ContextConfig
config: ContextConfig
): string {
const title = obs.title || 'Untitled';
const icon = ModeManager.getInstance().getTypeIcon(obs.type);
const time = timeDisplay ? compactTime(timeDisplay) : '"';
const refId = formatContextReferenceId(obs.id, config);

return `${obs.id} ${time} ${icon} ${title}`;
return `${refId} ${time} ${icon} ${title}`;
}

export function renderAgentFullObservation(
Expand All @@ -110,8 +116,9 @@ export function renderAgentFullObservation(
const icon = ModeManager.getInstance().getTypeIcon(obs.type);
const time = timeDisplay ? compactTime(timeDisplay) : '"';
const { readTokens, discoveryDisplay } = formatObservationTokenDisplay(obs, config);
const refId = formatContextReferenceId(obs.id, config);

output.push(`**${obs.id}** ${time} ${icon} **${title}**`);
output.push(`**${refId}** ${time} ${icon} **${title}**`);
if (detailField) {
output.push(detailField);
}
Expand Down
12 changes: 8 additions & 4 deletions src/services/context/formatters/HumanFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
import { colors } from '../types.js';
import { ModeManager } from '../../domain/ModeManager.js';
import { formatObservationTokenDisplay } from '../TokenCalculator.js';
import { formatContextReferenceId } from './id-display.js';

function formatHeaderDateTime(): string {
const now = new Date();
Expand Down Expand Up @@ -49,12 +50,15 @@ export function renderHumanColumnKey(): string[] {
];
}

export function renderHumanContextIndex(): string[] {
export function renderHumanContextIndex(fetchByIdSupported: boolean = true): string[] {
const drilldownLine = fetchByIdSupported
? `${colors.dim} - Fetch by ID: get_observations([IDs]) for observations visible in this index${colors.reset}`
: `${colors.dim} - Search: observation_search / mem-search skill (by-id fetch is not available in server-beta mode)${colors.reset}`;
return [
`${colors.dim}Context Index: This semantic index (titles, types, files, tokens) is usually sufficient to understand past work.${colors.reset}`,
'',
`${colors.dim}When you need implementation details, rationale, or debugging context:${colors.reset}`,
`${colors.dim} - Fetch by ID: get_observations([IDs]) for observations visible in this index${colors.reset}`,
drilldownLine,
`${colors.dim} - Search history: Use the mem-search skill for past decisions, bugs, and deeper research${colors.reset}`,
`${colors.dim} - Trust this index over re-reading code for past decisions and learnings${colors.reset}`,
''
Expand Down Expand Up @@ -114,7 +118,7 @@ export function renderHumanTableRow(
const readPart = (config.showReadTokens && readTokens > 0) ? `${colors.dim}(~${readTokens}t)${colors.reset}` : '';
const discoveryPart = (config.showWorkTokens && discoveryTokens > 0) ? `${colors.dim}(${workEmoji} ${discoveryTokens.toLocaleString()}t)${colors.reset}` : '';

return ` ${colors.dim}#${obs.id}${colors.reset} ${timePart} ${icon} ${title} ${readPart} ${discoveryPart}`;
return ` ${colors.dim}#${formatContextReferenceId(obs.id, config)}${colors.reset} ${timePart} ${icon} ${title} ${readPart} ${discoveryPart}`;
}

export function renderHumanFullObservation(
Expand All @@ -133,7 +137,7 @@ export function renderHumanFullObservation(
const readPart = (config.showReadTokens && readTokens > 0) ? `${colors.dim}(~${readTokens}t)${colors.reset}` : '';
const discoveryPart = (config.showWorkTokens && discoveryTokens > 0) ? `${colors.dim}(${workEmoji} ${discoveryTokens.toLocaleString()}t)${colors.reset}` : '';

output.push(` ${colors.dim}#${obs.id}${colors.reset} ${timePart} ${icon} ${colors.bright}${title}${colors.reset}`);
output.push(` ${colors.dim}#${formatContextReferenceId(obs.id, config)}${colors.reset} ${timePart} ${icon} ${colors.bright}${title}${colors.reset}`);
if (detailField) {
output.push(` ${colors.dim}${detailField}${colors.reset}`);
}
Expand Down
26 changes: 26 additions & 0 deletions src/services/context/formatters/id-display.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { ContextConfig } from '../types.js';

const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const SHORT_UUID_LENGTH = 8;

/**
* Format an observation id for display in the injected context panel.
*
* When by-id fetch is unsupported (server-beta / Postgres mode, where ids are
* UUIDs and the inject ref is not a fetch handle), UUID ids are abbreviated to
* their 8-char prefix to cut tokenizer cost — full UUIDs fragment badly and are
* the worst case for the tokenizer. These short refs are display-only; lookups
* go by title/semantic search and the full UUID still lives in search results
* where tokens are not the bottleneck. Everything else (numeric SQLite ids,
* fetch-by-id modes) is left unchanged.
*/
export function formatContextReferenceId(
id: string | number,
config: Pick<ContextConfig, 'fetchByIdSupported'>
): string {
const value = String(id);
if (config.fetchByIdSupported === false && UUID_RE.test(value)) {
return value.slice(0, SHORT_UUID_LENGTH);
}
return value;
}
5 changes: 3 additions & 2 deletions src/services/context/sections/HeaderRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export function renderHeader(
forHuman: boolean
): string[] {
const output: string[] = [];
const fetchByIdSupported = config.fetchByIdSupported !== false;

if (forHuman) {
output.push(...Human.renderHumanHeader(project));
Expand All @@ -21,7 +22,7 @@ export function renderHeader(
if (forHuman) {
output.push(...Human.renderHumanLegend());
} else {
output.push(...Agent.renderAgentLegend());
output.push(...Agent.renderAgentLegend(fetchByIdSupported));
}

if (forHuman) {
Expand All @@ -31,7 +32,7 @@ export function renderHeader(
}

if (forHuman) {
output.push(...Human.renderHumanContextIndex());
output.push(...Human.renderHumanContextIndex(fetchByIdSupported));
} else {
output.push(...Agent.renderAgentContextIndex());
}
Expand Down
8 changes: 8 additions & 0 deletions src/services/context/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ export interface ContextConfig {
fullObservationField: 'narrative' | 'facts';
showLastSummary: boolean;
showLastMessage: boolean;

/**
* Whether observation refs in the inject panel can be fetched by id.
* When false (server-beta / Postgres UUID mode), refs are abbreviated to an
* 8-char prefix (display-only) and the legend points to title/semantic search.
* Defaults to true (full id shown) when omitted — backward compatible.
*/
fetchByIdSupported?: boolean;
}

export interface Observation {
Expand Down
31 changes: 31 additions & 0 deletions tests/context/formatters/agent-formatter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
} from '../../../src/services/context/formatters/AgentFormatter.js';

import type { Observation, TokenEconomics, ContextConfig, PriorMessages } from '../../../src/services/context/types.js';
import { formatContextReferenceId } from '../../../src/services/context/formatters/id-display.js';

function createTestObservation(overrides: Partial<Observation> = {}): Observation {
return {
Expand Down Expand Up @@ -130,6 +131,16 @@ describe('AgentFormatter', () => {

expect(result[0]).toContain('session');
});

it('should keep get_observations hint when fetch-by-id is supported (default)', () => {
expect(renderAgentLegend(true).join('\n')).toContain('get_observations');
});

it('should switch to display-only refs hint when fetch-by-id is unsupported', () => {
const joined = renderAgentLegend(false).join('\n');
expect(joined).toContain('short refs are display-only');
expect(joined).not.toContain('get_observations');
});
});

describe('renderAgentColumnKey', () => {
Expand Down Expand Up @@ -460,3 +471,23 @@ describe('AgentFormatter', () => {
});
});
});

describe('formatContextReferenceId', () => {
const UUID = '3c4b2513-5048-45fa-95e0-e3222ae99671';

it('abbreviates UUID ids to their 8-char prefix when fetch-by-id is unsupported', () => {
expect(formatContextReferenceId(UUID, { fetchByIdSupported: false })).toBe('3c4b2513');
});

it('keeps the full UUID when fetch-by-id is supported', () => {
expect(formatContextReferenceId(UUID, { fetchByIdSupported: true })).toBe(UUID);
});

it('defaults to the full id when fetchByIdSupported is omitted (backward compatible)', () => {
expect(formatContextReferenceId(UUID, {})).toBe(UUID);
});

it('leaves non-UUID (numeric) ids unchanged even when fetch-by-id is unsupported', () => {
expect(formatContextReferenceId(42, { fetchByIdSupported: false })).toBe('42');
});
});
Loading