feat(setup): wizard prompt tweaks and quieter query-history filter output (#259)

Setup wizard flow tweaks:
- Add a reveal-tail password prompt (reveal-password-prompt.ts) that unmasks
  the last few characters of a typed/pasted secret, and wire it into the setup
  prompt adapter in place of clack's password(); adds the @clack/core dep.
- Reorder wizard select options: surface "Paste a key" before the
  environment-variable option across embeddings/models/sources, promote
  Metabase/Notion in the source list, put Git URL before Local path, reorder
  the Notion crawl-mode choices, and relabel the sources "Done" action.

Query-history filter picker output:
- Collapse the per-template parse-failure lines into a single count in the
  setup output and route the full template-id list to --debug stderr.
- Model parse failures as a structured parseFailedTemplateIds field instead of
  warning strings.
- Add a privacy-safe query_history_filter_completed telemetry event
  (counts/enums only), mirrored into the Python daemon schema.
This commit is contained in:
Andrey Avtomonov 2026-06-04 14:11:08 +02:00 committed by GitHub
parent 8eb1cd3e79
commit c2beaf7d55
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 494 additions and 34 deletions

View file

@ -64,6 +64,27 @@ function sqlAnalysis(tablesById: Record<string, Array<{ catalog: string | null;
};
}
function sqlAnalysisWithErrors(
tablesById: Record<string, Array<{ catalog: string | null; db: string | null; name: string }>>,
errorIds: string[],
): SqlAnalysisPort {
const errors = new Set(errorIds);
return {
analyzeForFingerprint: vi.fn(),
analyzeBatch: vi.fn(async (items: SqlAnalysisBatchItem[]): Promise<Map<string, SqlAnalysisBatchResult>> =>
new Map<string, SqlAnalysisBatchResult>(
items.map((item) => [
item.id,
errors.has(item.id)
? { tablesTouched: [], columnsByClause: {}, error: 'parse boom' }
: { tablesTouched: tablesById[item.id] ?? [], columnsByClause: {} },
]),
),
),
validateReadOnly: vi.fn(async () => ({ ok: true })),
};
}
function llm(decisions: Array<{ role: string; exclude: boolean; reason: string }>): KtxLlmRuntimePort {
const generateObject = vi.fn(async () => ({ roles: decisions })) as KtxLlmRuntimePort['generateObject'];
return {
@ -198,6 +219,7 @@ describe('query-history filter picker', () => {
consideredRoleCount: 0,
skipped: { reason: 'no-llm' },
warnings: [],
parseFailedTemplateIds: [],
});
});
@ -227,6 +249,32 @@ describe('query-history filter picker', () => {
expect(proposal.skipped).toEqual({ reason: 'no-in-scope-history' });
});
it('records parse failures as template ids, not warnings', async () => {
const proposal = await proposeQueryHistoryServiceAccountFilters({
connectionId: 'warehouse',
dialect: 'postgres',
queryClient: {},
reader: reader(
aggregate({
templateId: 'good',
canonicalSql: 'select * from analytics.orders',
topUsers: [{ user: 'analyst', executions: 30 }],
}),
aggregate({
templateId: 'broken',
canonicalSql: 'select * from where',
topUsers: [{ user: 'analyst', executions: 5 }],
}),
),
sqlAnalysis: sqlAnalysisWithErrors({ good: [{ catalog: null, db: 'analytics', name: 'orders' }] }, ['broken']),
llmRuntime: llm([]),
pullConfig: { dialect: 'postgres', enabledSchemas: ['analytics'], filters: { dropTrivialProbes: true } },
});
expect(proposal.parseFailedTemplateIds).toEqual(['broken']);
expect(proposal.warnings).toEqual([]);
});
it('keeps clean in-scope history when the model excludes nothing', async () => {
const proposal = await proposeQueryHistoryServiceAccountFilters({
connectionId: 'warehouse',