Repository navigation
Implement repo-backed source foundation #181
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
eac7a20
0a4be2f
2324b0b
e196933
6c51427
fe0612a
82c3691
89aa1ff
2e02b20
4d59ddc
c190672
ce949d4
92bb614
027f906
1808a6a
cc43708
ebba2ba
fb19e40
806166e
a88ceaa
0596d81
13326c9
4f1a8c0
cd7f099
e1e1246
a71e5d8
26250cb
d6ce000
69331f2
1915391
571464b
6fd87ea
fa0289c
f054138
74d9760
688097d
ad414e8
7d96125
ee15fe3
1f4da7c
e544738
ddb50b0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| CREATE TABLE IF NOT EXISTS entity_sources ( | ||
| id TEXT PRIMARY KEY, | ||
| user_id TEXT NOT NULL, | ||
| entity_kind TEXT NOT NULL, | ||
| entity_id TEXT NOT NULL, | ||
| repo_id TEXT NOT NULL, | ||
| published_commit TEXT, | ||
| indexed_commit TEXT, | ||
| manifest_path TEXT NOT NULL DEFAULT 'kody.json', | ||
| source_root TEXT NOT NULL DEFAULT '/', | ||
| created_at TEXT NOT NULL, | ||
| updated_at TEXT NOT NULL | ||
| ); | ||
|
|
||
| CREATE UNIQUE INDEX IF NOT EXISTS idx_entity_sources_user_entity | ||
| ON entity_sources(user_id, entity_kind, entity_id); | ||
|
|
||
| CREATE UNIQUE INDEX IF NOT EXISTS idx_entity_sources_repo_id | ||
| ON entity_sources(repo_id); | ||
|
|
||
| CREATE INDEX IF NOT EXISTS idx_entity_sources_user_id | ||
| ON entity_sources(user_id); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| CREATE TABLE IF NOT EXISTS repo_sessions ( | ||
| id TEXT PRIMARY KEY, | ||
| user_id TEXT NOT NULL, | ||
| source_id TEXT NOT NULL, | ||
| session_repo_id TEXT NOT NULL, | ||
| session_repo_name TEXT NOT NULL, | ||
| session_repo_namespace TEXT NOT NULL DEFAULT 'default', | ||
| base_commit TEXT NOT NULL, | ||
| source_root TEXT NOT NULL DEFAULT '/', | ||
| conversation_id TEXT, | ||
| status TEXT NOT NULL DEFAULT 'active', | ||
| expires_at TEXT, | ||
| last_checkpoint_at TEXT, | ||
| last_checkpoint_commit TEXT, | ||
| last_check_run_id TEXT, | ||
| last_check_tree_hash TEXT, | ||
| created_at TEXT NOT NULL, | ||
| updated_at TEXT NOT NULL | ||
| ); | ||
|
|
||
| CREATE INDEX IF NOT EXISTS idx_repo_sessions_user_id | ||
| ON repo_sessions(user_id); | ||
|
|
||
| CREATE INDEX IF NOT EXISTS idx_repo_sessions_source_id | ||
| ON repo_sessions(source_id); | ||
|
|
||
| CREATE INDEX IF NOT EXISTS idx_repo_sessions_conversation_id | ||
| ON repo_sessions(conversation_id); | ||
|
|
||
| ALTER TABLE mcp_skills | ||
| ADD COLUMN source_id TEXT; | ||
|
|
||
| CREATE INDEX IF NOT EXISTS idx_mcp_skills_source_id | ||
| ON mcp_skills(source_id); | ||
|
|
||
| ALTER TABLE ui_artifacts | ||
| ADD COLUMN source_id TEXT; | ||
|
|
||
| CREATE INDEX IF NOT EXISTS idx_ui_artifacts_source_id | ||
| ON ui_artifacts(source_id); | ||
|
|
||
| ALTER TABLE jobs | ||
| ADD COLUMN source_id TEXT; | ||
|
|
||
| ALTER TABLE jobs | ||
| ADD COLUMN published_commit TEXT; | ||
|
|
||
| CREATE INDEX IF NOT EXISTS idx_jobs_source_id | ||
| ON jobs(source_id); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import { reindexJobVectors } from './jobs/job-reindex.ts' | ||
|
|
||
| export async function handleJobReindexRequest( | ||
| request: Request, | ||
| env: Env, | ||
| ): Promise<Response> { | ||
| if (request.method !== 'POST') { | ||
| return new Response('Method Not Allowed', { status: 405 }) | ||
| } | ||
|
|
||
| const secret = env.JOB_REINDEX_SECRET?.trim() | ||
| if (!secret) { | ||
| return new Response('Job reindex is not configured', { status: 503 }) | ||
| } | ||
|
|
||
| const auth = request.headers.get('Authorization')?.trim() | ||
| const token = auth?.startsWith('Bearer ') | ||
| ? auth.slice('Bearer '.length).trim() | ||
| : null | ||
| if (token !== secret) { | ||
| return new Response('Unauthorized', { status: 401 }) | ||
| } | ||
|
|
||
| try { | ||
| const { upserted } = await reindexJobVectors(env) | ||
| return Response.json({ ok: true, upserted }) | ||
| } catch (error) { | ||
| const message = error instanceof Error ? error.message : String(error) | ||
| return Response.json({ ok: false, error: message }, { status: 500 }) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| import { | ||
| getCapabilityVectorIndex, | ||
| isCapabilitySearchOffline, | ||
| } from '#mcp/capabilities/capability-search.ts' | ||
| import { buildJobEmbedText } from '#mcp/jobs-embed.ts' | ||
| import { jobVectorId } from '#mcp/jobs-vectorize.ts' | ||
| import { listJobs } from './service.ts' | ||
|
|
||
| const upsertBatchSize = 16 | ||
|
|
||
| type JobEmbeddingBatchResult = { data?: Array<Array<number>> } | ||
|
|
||
| export async function reindexJobVectors( | ||
| env: Env, | ||
| ): Promise<{ upserted: number }> { | ||
| const index = getCapabilityVectorIndex(env) | ||
| if (!index) { | ||
| throw new Error('CAPABILITY_VECTOR_INDEX binding is not configured') | ||
| } | ||
| if (isCapabilitySearchOffline(env)) { | ||
| return { upserted: 0 } | ||
| } | ||
|
|
||
| const rows = await env.APP_DB.prepare( | ||
| `SELECT DISTINCT user_id FROM jobs WHERE source_id IS NOT NULL OR code IS NOT NULL`, | ||
| ).all<{ user_id: string }>() | ||
| const userIds = (rows.results ?? []).map((row) => row.user_id).filter(Boolean) | ||
| if (userIds.length === 0) return { upserted: 0 } | ||
|
|
||
| const jobs = ( | ||
| await Promise.all( | ||
| userIds.map((userId) => | ||
| listJobs({ | ||
| env, | ||
| userId, | ||
| }).then((userJobs) => userJobs.map((job) => ({ userId, job }))), | ||
| ), | ||
| ) | ||
| ).flat() | ||
| if (jobs.length === 0) return { upserted: 0 } | ||
|
|
||
| let upserted = 0 | ||
| for (let offset = 0; offset < jobs.length; offset += upsertBatchSize) { | ||
| const batch = jobs.slice(offset, offset + upsertBatchSize) | ||
| const texts = batch.map(({ job }) => | ||
| buildJobEmbedText({ | ||
| name: job.name, | ||
| scheduleSummary: job.scheduleSummary, | ||
| sourceId: job.sourceId, | ||
| publishedCommit: job.publishedCommit, | ||
| }), | ||
| ) | ||
|
Comment on lines
+45
to
+52
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Confirm buildJobEmbedText's expected inputs and how the primary write path calls it.
fd -t f 'jobs-embed.ts' | xargs -I{} sed -n '1,120p' {}
rg -nP -C3 '\bbuildJobEmbedText\s*\('Repository: kentcdodds/kody Length of output: 3613 🏁 Script executed: #!/bin/bash
# Check the Job type definition to see if description field exists
rg -nP 'type Job|interface Job' --max-count=10
rg -nP '\bjob\.description\b'
# Also check if any entity has a description field
rg -nP '\bdescription:' packages/worker/src/jobs/types.ts -A2 -B2Repository: kentcdodds/kody Length of output: 3031
🐛 Proposed fix const texts = batch.map(({ job }) =>
buildJobEmbedText({
name: job.name,
- description: job.name,
+ description: job.description,
scheduleSummary: job.scheduleSummary,
sourceId: job.sourceId,
publishedCommit: job.publishedCommit,
}),
)Note: The same bug exists in 🤖 Prompt for AI Agents |
||
| const result = (await env.AI.run('@cf/baai/bge-small-en-v1.5', { | ||
| text: texts, | ||
| pooling: 'mean', | ||
| })) as JobEmbeddingBatchResult | ||
| const vectors = result.data | ||
| if (!vectors || vectors.length !== batch.length) { | ||
| throw new Error('Workers AI embedding batch size mismatch for jobs') | ||
| } | ||
| await index.upsert( | ||
| batch.map(({ userId, job }, index_) => ({ | ||
| id: jobVectorId(job.id), | ||
| values: vectors[index_]!, | ||
| metadata: { kind: 'job', userId }, | ||
| })), | ||
| ) | ||
| upserted += batch.length | ||
| } | ||
|
|
||
| return { upserted } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.