feat: projects as backend sources with system prompt injection (#8739)

Signed-off-by: Douwe Osinga <douwe@squareup.com>
Co-authored-by: Douwe Osinga <douwe@squareup.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Douwe Osinga
2026-05-07 10:30:15 -04:00
committed by GitHub
parent bced4ea5ec
commit ff42171903
29 changed files with 1241 additions and 919 deletions
+40 -5
View File
@@ -752,12 +752,24 @@ export type CreateSourceRequest = {
* Absolute path to the project root. Required when `global` is false.
*/
projectDir?: string | null;
/**
* Project source ID. When set with `global: false`, the backend resolves
* the project's first working directory automatically. Takes precedence
* over `project_dir`.
*/
projectId?: string | null;
/**
* Arbitrary key/value metadata.
*/
properties?: {
[key: string]: unknown;
};
};
/**
* The type of source entity.
*/
export type SourceType = 'skill' | 'builtinSkill' | 'recipe' | 'subrecipe' | 'agent';
export type SourceType = 'skill' | 'builtinSkill' | 'recipe' | 'subrecipe' | 'agent' | 'project';
export type CreateSourceResponse = {
source: SourceEntry;
@@ -774,11 +786,12 @@ export type SourceEntry = {
description: string;
content: string;
/**
* Absolute path to the source on disk. A directory for skills, a file for
* recipes and agents. Built-in skills use read-only synthetic
* `builtin://skills/<name>` paths.
* Stable on-disk path identifying this source. Pass it back to
* update/delete/export to operate on this entry. Skills use the directory
* containing `SKILL.md`; projects use the project file path; built-in
* skills use `builtin://skills/<name>` synthetic paths.
*/
directory: string;
path: string;
/**
* True when the source lives in the user's global sources directory; false
* when it lives inside a specific project.
@@ -789,6 +802,13 @@ export type SourceEntry = {
* Only skills currently populate this; empty for other source types.
*/
supportingFiles?: Array<string>;
/**
* Arbitrary key/value pairs for type-specific metadata (e.g. icon, color,
* preferredProvider for projects). Stored in the frontmatter.
*/
properties?: {
[key: string]: unknown;
};
};
/**
@@ -802,6 +822,11 @@ export type SourceEntry = {
export type ListSourcesRequest = {
type?: SourceType | null;
projectDir?: string | null;
/**
* When true, also scan the working directories of all known projects for
* project-scoped sources (e.g. skills stored under `{workingDir}/.agents/skills/`).
*/
includeProjectSources?: boolean;
};
export type ListSourcesResponse = {
@@ -817,6 +842,16 @@ export type UpdateSourceRequest = {
name: string;
description: string;
content: string;
/**
* When `Some`, replaces all stored properties on the source. When
* `None` (or omitted), the source's existing properties are
* preserved. Callers that don't model the full property bag (e.g.
* the skills editor, which only edits name/description/content)
* should omit this so per-skill metadata isn't silently erased.
*/
properties?: {
[key: string]: unknown;
} | null;
};
export type UpdateSourceResponse = {
+18 -6
View File
@@ -755,7 +755,8 @@ export const zSourceType = z.enum([
'builtinSkill',
'recipe',
'subrecipe',
'agent'
'agent',
'project'
]);
/**
@@ -770,7 +771,12 @@ export const zCreateSourceRequest = z.object({
projectDir: z.union([
z.string(),
z.null()
]).optional()
]).optional(),
projectId: z.union([
z.string(),
z.null()
]).optional(),
properties: z.record(z.unknown()).optional()
});
/**
@@ -783,9 +789,10 @@ export const zSourceEntry = z.object({
name: z.string(),
description: z.string(),
content: z.string(),
directory: z.string(),
path: z.string(),
global: z.boolean(),
supportingFiles: z.array(z.string()).optional()
supportingFiles: z.array(z.string()).optional(),
properties: z.record(z.unknown()).optional()
});
export const zCreateSourceResponse = z.object({
@@ -808,7 +815,8 @@ export const zListSourcesRequest = z.object({
projectDir: z.union([
z.string(),
z.null()
]).optional()
]).optional(),
includeProjectSources: z.boolean().optional().default(false)
});
export const zListSourcesResponse = z.object({
@@ -823,7 +831,11 @@ export const zUpdateSourceRequest = z.object({
path: z.string(),
name: z.string(),
description: z.string(),
content: z.string()
content: z.string(),
properties: z.union([
z.record(z.unknown()),
z.null()
]).optional()
});
export const zUpdateSourceResponse = z.object({