Skip to content
Merged
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
6 changes: 6 additions & 0 deletions compose.development.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ services:
TREESEED_MAILPIT_SMTP_HOST:
TREESEED_MAILPIT_SMTP_PORT:
TREESEED_TREEDX_URL:
TREESEED_LOCAL_TREEDX_HOSTS: treedx,host.docker.internal
TREESEED_TREEDX_JWT_ISSUER:
TREESEED_TREEDX_JWT_AUDIENCE:
POSTGRES_USER:
Expand All @@ -36,6 +37,8 @@ services:
- "127.0.0.1:3000:3000"
volumes:
- ${TREESEED_DEVELOPMENT_WORKSPACE_ROOT:?TreeSeed development workspace root is required}:${TREESEED_DEVELOPMENT_WORKSPACE_ROOT:?TreeSeed development workspace root is required}:ro
extra_hosts:
- "host.docker.internal:host-gateway"
tmpfs:
- /tmp
networks:
Expand Down Expand Up @@ -63,6 +66,7 @@ services:
TREESEED_COMPONENT_DATA_ROOT:
TREESEED_API_BASE_URL:
TREESEED_TREEDX_URL:
TREESEED_LOCAL_TREEDX_HOSTS: treedx,host.docker.internal
TREESEED_TREEDX_JWT_ISSUER:
TREESEED_TREEDX_JWT_AUDIENCE:
TREESEED_DATABASE_URL:
Expand All @@ -80,6 +84,8 @@ services:
TREESEED_DEVELOPMENT_MODE:
volumes:
- ${TREESEED_DEVELOPMENT_WORKSPACE_ROOT:?TreeSeed development workspace root is required}:${TREESEED_DEVELOPMENT_WORKSPACE_ROOT:?TreeSeed development workspace root is required}:ro
extra_hosts:
- "host.docker.internal:host-gateway"
tmpfs:
- /tmp
networks:
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
"@modelcontextprotocol/server": "2.0.0",
"@octokit/auth-app": "^8.2.0",
"@react-email/render": "^2.0.8",
"@treeseed/sdk": "0.13.0-rc.59",
"@treeseed/sdk": "0.13.0-rc.66",
"@treeseed/treedx": "0.3.0-rc.3",
"drizzle-orm": "^0.45.2",
"hono": "4.13.3",
Expand Down
3 changes: 3 additions & 0 deletions src/api/app/support/runtime/foundation-runtime-utilities.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getSiteAuthConfig } from '../../../../auth/config.ts';
import { backfillUserEmailAddresses,normalizeBaseUrl,parseBooleanEnvValue,redactedRequestTarget } from '../index.ts';
import { reconcileManagedTeamLibraries } from '../../../teams/managed-team-library-service.ts';
export async function accountDeletionBlockers(store, principal) {
const teams = await store.listTeamsForPrincipal(principal);
const blockers = teams
Expand Down Expand Up @@ -83,6 +84,8 @@ export function requestClientIp(c) {
export async function ensureControlPlaneCredentialSchema(store) {
await store.ensureInitialized();
await backfillUserEmailAddresses(store);
await store.backfillManagedTeamLibraryProjects();
if(String(process.env.TREESEED_GITHUB_TOKEN??'').trim())await reconcileManagedTeamLibraries(store,process.env);
}
export function sanitizedReturnTo(value) {
const target = String(value ?? '/app/');
Expand Down
2 changes: 1 addition & 1 deletion src/api/capacity/policy/workdays/chat-activity-profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function compileDefaultChatActivityProfile(
enabled: true,
handler: 'writer',
prompt: {
system: `Participate as ${agentSlug} in a TreeSeed Discussion. Begin at the exact project repository root, read its AGENTS.md, and inspect source, scripts, tests, and CI whenever the question depends on implementation evidence. Use the assignment's exact project TreeDX library as the default knowledge context. Answer from your configured identity and durable instructions, cite exact TreeDX content or repository refs, distinguish evidence from inference, and keep the response scoped to the current turn. If either repository or TreeDX read context is missing, report an execution-context defect instead of asking the user to supply files owned by the project. You may create or update discussion messages, linked notes, questions, and proposals. Questions must declare their owning project, requested audience, related objectives, and answer policy so they can enter the team inbox. Proposals must declare their owning project, proposal type, evidence, objective links, and complete plan; never describe a proposal as approved until an exact-version governed inbox action accepts it. When human input is required, create a durable question instead of burying the request in prose. Never change knowledge or code without an approved governed acting assignment.${specialization.responseStyle ? ` Response style: ${specialization.responseStyle}` : ''}`,
system: `Participate as ${agentSlug} in a TreeSeed Discussion. Use the mandatory context pack, agent-wide TreeDX context queries, and this activity profile's additive queries before making focused follow-up TreeDX reads or searches. Do not expect or request a wholesale code or knowledge repository snapshot for chat. Answer from your configured identity and durable instructions, cite exact TreeDX content, distinguish evidence from inference, and keep the response scoped to the current turn. If required TreeDX query or tool context is missing, report an execution-context defect instead of asking the user to supply files owned by the project. You may create or update discussion messages, linked notes, questions, and proposals. Questions must declare their owning project, requested audience, related objectives, and answer policy so they can enter the team inbox. Proposals must declare their owning project, proposal type, evidence, objective links, and complete plan; never describe a proposal as approved until an exact-version governed inbox action accepts it. When human input is required, create a durable question instead of burying the request in prose. Never change knowledge or code without an approved governed acting assignment.${specialization.responseStyle ? ` Response style: ${specialization.responseStyle}` : ''}`,
task: specialization.promptTask ?? 'Respond to the committed Discussion turn and produce durable, source-grounded output.',
},
branchPolicy: { kind: 'staging-content', base: 'staging' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export function upsertCapacityExecutionProviderOperations(input: {
}) : [];
const offerOperations: CapacityDatabaseOperation[] = Array.isArray(entry.offers) ? entry.offers.map((offerValue) => {
const offer = record(offerValue);
return { query: `INSERT INTO execution_capability_offers (capacity_provider_id,execution_provider_id,offer_id,offer_digest,offer_json,status,last_seen_at) VALUES (?,?,?,?,?,'active',?) ON CONFLICT (capacity_provider_id,offer_id) DO UPDATE SET execution_provider_id=EXCLUDED.execution_provider_id,offer_digest=EXCLUDED.offer_digest,offer_json=EXCLUDED.offer_json,status='active',last_seen_at=EXCLUDED.last_seen_at`, params: [input.providerId,id,String(offer.offerId),String(offer.offerDigest),JSON.stringify(offer),input.createdAt] };
return { query: `INSERT INTO execution_capability_offers (capacity_provider_id,execution_provider_id,offer_id,offer_digest,offer_json,status,last_seen_at) VALUES (?,?,?,?,?,'active',?) ON CONFLICT (capacity_provider_id,offer_id) DO UPDATE SET execution_provider_id=EXCLUDED.execution_provider_id,offer_digest=EXCLUDED.offer_digest,offer_json=EXCLUDED.offer_json,status=CASE WHEN execution_capability_offers.status='context_overflow' AND execution_capability_offers.offer_digest=EXCLUDED.offer_digest THEN 'context_overflow' ELSE 'active' END,last_seen_at=EXCLUDED.last_seen_at`, params: [input.providerId,id,String(offer.offerId),String(offer.offerDigest),JSON.stringify(offer),input.createdAt] };
}) : [];
return [executionProviderOperation, ...laneOperations, ...offerOperations];
});
Expand Down
7 changes: 5 additions & 2 deletions src/api/capacity/services/build/demand-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,8 +327,10 @@ async function compilePlanningDemands(
: { requestedSeconds: estimate.seconds, providerFloor: 0, floorSource: 'unbounded-session' };
const requestedSeconds = allocation.requestedSeconds;
const contextReferences=[
...agent.contextQueryRefs.map((reference)=>({kind:'query' as const,...reference})),
...agent.contextQuerySetRefs.map((reference)=>({kind:'query-set' as const,...reference})),
...agent.contextQueryLayers.agent.queryRefs.map((reference)=>({kind:'query' as const,...reference,layer:'agent' as const})),
...agent.contextQueryLayers.agent.querySetRefs.map((reference)=>({kind:'query-set' as const,...reference,layer:'agent' as const})),
...agent.contextQueryLayers.activity.queryRefs.map((reference)=>({kind:'query' as const,...reference,layer:'activity' as const})),
...agent.contextQueryLayers.activity.querySetRefs.map((reference)=>({kind:'query-set' as const,...reference,layer:'activity' as const})),
];
const definitionBaseRef=capacityWorkdayContentBaseRef(run.environment,agent.branchPolicy,agent.sourceImmutableRef);
const contentBaseRef=capacityWorkdayRuntimeContentRef(source.payload,definitionBaseRef);
Expand All @@ -354,6 +356,7 @@ async function compilePlanningDemands(
permissions: agent.permissions, tools: agent.toolPolicy, capabilityRequirements: agent.capabilityRequirements } : undefined,
groupIds:agent.groupIds,
contextQueryRefs:contextReferences,
contextQueryLayers:agent.contextQueryLayers,
instructionTemplateRefs:agent.instructionTemplateRefs,
contextQueryChecks:verifiedContext.map((check)=>({ id:check.id,testId:check.testId,testRef:check.testRef,definition:check.definition,
checkedAt:check.checkedAt,expiresAt:check.expiresAt,latencyMs:check.latencyMs,stats:check.stats,assertions:check.assertions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ function safeId(value:unknown) {
}
function path(root:string,collection:string,id:string) { return projectLibraryPath(root, collection, `${safeId(id)}.mdx`); }
function digest(value:unknown) { return createHash('sha256').update(JSON.stringify(value)).digest('hex'); }
function strings(value:unknown) { return Array.isArray(value)?[...new Set(value.map(String).map((item)=>item.trim()).filter(Boolean))]:[]; }
export async function executeCurrentContext(connection:Awaited<ReturnType<typeof resolveKnowledgeGatewayConnection>>,exactRef:string,request:Record<string,unknown>) {
if(!connection) throw new CapacityGovernanceError('context_query_treedx_unavailable','Project TreeDX content is unavailable.',409);
// A successful context response is not proof that TreeDX's derived graph includes
Expand Down Expand Up @@ -52,6 +53,43 @@ function rowCheck(row:Record<string,unknown>) {
export class ContextQueryCheckService {
constructor(private readonly store:CapacityGovernanceDatabase) {}

private async queryProjects(teamId:string,projectId:string,query:DeclarativeContextQuery) {
const sources=query.sources?.length?query.sources:[{scope:'current-project' as const}],projects=new Map<string,{projectId:string;paths:string[];source:string}>();
const add=(id:string,paths:string[],source:string)=>projects.set(id,{projectId:id,paths:paths.length?paths:['**'],source});
for(const selector of sources) {
if(selector.scope==='current-project') { add(projectId,['**'],'current-project'); continue; }
if(selector.scope==='team-library') {
const teamProject=await (this.store as any).getProjectByTeamAndSlug(teamId,'team');
if(!teamProject) throw new CapacityGovernanceError('team_library_unavailable','The managed Team Library is unavailable.',409);
add(String(teamProject.id),['**'],'team-library'); continue;
}
if(selector.scope==='same-team') {
const teamProjects=await (this.store as any).listTeamProjects(teamId),ids=new Set(selector.projectIds??[]),slugs=new Set(selector.projectSlugs??[]);
const selected=ids.size||slugs.size?teamProjects.filter((project:Record<string,unknown>)=>ids.has(String(project.id))||slugs.has(String(project.slug))):teamProjects;
if(selected.length!==(ids.size+slugs.size||teamProjects.length))throw new CapacityGovernanceError('context_query_source_missing','A selected same-team project does not exist.',404);
for(const project of selected)add(String(project.id),['**'],'same-team');continue;
}
const shares=(await (this.store as any).listTreeDxSharesForRecipient(teamId)).filter((share:Record<string,unknown>)=>String(share.teamId)===selector.teamId&&share.status==='active'&&(!share.expiresAt||Date.parse(String(share.expiresAt))>Date.now()));
const eligible=new Map<string,Record<string,unknown>>();for(const share of shares){const grant=record(share.trustGrant);if(!strings(grant.operations).includes('context'))continue;for(const id of strings(grant.projectIds??share.projectId))eligible.set(id,grant);}
const selected=selector.projectIds?.length?selector.projectIds:[...eligible.keys()];
for(const id of selected){const grant=eligible.get(id);if(!grant)throw new CapacityGovernanceError('context_query_share_denied','An active knowledge share does not cover the selected project.',403);add(id,strings(grant.paths),`shared-team:${selector.teamId}`);}
}
return [...projects.values()];
}

private async executeQuerySources(teamId:string,projectId:string,query:DeclarativeContextQuery,request:Record<string,unknown>) {
const selected=await this.queryProjects(teamId,projectId,query),results=[] as Array<{projectId:string;source:string;ref:string;result:any}>;
for(const source of selected) {
const connection=await resolveKnowledgeGatewayConnection(this.store,{projectId:source.projectId,write:true,authoringPaths:true});
if(!connection)throw new CapacityGovernanceError('context_query_treedx_unavailable',`TreeDX content is unavailable for source project ${source.projectId}.`,409);
const result=await executeCurrentContext(connection,connection.baseRef,{...request,scopePaths:source.paths});
results.push({projectId:source.projectId,source:source.source,ref:connection.baseRef,result});
}
const unpack=(value:any)=>record(record(value).payload??value),nodes=results.flatMap((entry)=>Array.isArray(unpack(entry.result).nodes)?unpack(entry.result).nodes:[]),edges=results.flatMap((entry)=>Array.isArray(unpack(entry.result).edges)?unpack(entry.result).edges:[]);
return {nodes,edges,sources:results.map(({result,...source})=>{const value=unpack(result),sourceNodes=Array.isArray(value.nodes)?value.nodes.map(record):[];
return {...source,paths:[...new Set(sourceNodes.map((node)=>String(node.path??'').trim()).filter(Boolean))].sort()};}),memberResults:results.map((entry)=>entry.result)};
}

async definitionCommit(projectId:string) {
const connection=await resolveKnowledgeGatewayConnection(this.store,{projectId,write:false,authoringPaths:true});
if(!connection) return null;
Expand Down Expand Up @@ -125,13 +163,13 @@ export class ContextQueryCheckService {
// graph for the exact current ref, but it never writes or commits query results.
const exactConnection=await resolveKnowledgeGatewayConnection(this.store,{projectId,write:true,authoringPaths:true,readRefs:[exactRef]});
if(!exactConnection) throw new CapacityGovernanceError('context_query_treedx_unavailable','Project TreeDX content is unavailable.',409);
const execute=async(request:Record<string,unknown>)=>executeCurrentContext(exactConnection,exactRef,request);
let report:Record<string,unknown>; let definition:{kind:'query'|'query-set';id:string;revision:number;commit:string};
if(test.kind==='context-query') {
const querySource=await this.source(projectId,exactRef,path(initial.contentPath,COLLECTIONS.query,test.queryRef!.id));
const validation=validateContentFrontmatter('agent_context_query',querySource.frontmatter);
if(!validation.ok||!validation.data) throw new CapacityGovernanceError('context_query_definition_invalid','Context query definition is invalid.',422,{diagnostics:validation.diagnostics});
report=await executeContextQueryTest({query:validation.data as DeclarativeContextQuery,test,execute}) as Record<string,unknown>;
const query=validation.data as DeclarativeContextQuery;
report=await executeContextQueryTest({query,test,execute:(request)=>this.executeQuerySources(teamId,projectId,query,request)}) as Record<string,unknown>;
definition={kind:'query',id:test.queryRef!.id,revision:test.queryRef!.revision,commit:exactRef};
} else {
const setSource=await this.source(projectId,exactRef,path(initial.contentPath,COLLECTIONS.set,test.querySetRef!.id));
Expand All @@ -144,7 +182,7 @@ export class ContextQueryCheckService {
if(!validation.ok||!validation.data) throw new CapacityGovernanceError('context_query_definition_invalid','Query-set member is invalid.',422,{reference,diagnostics:validation.diagnostics});
queries.push(validation.data as DeclarativeContextQuery);
}
report=await executeContextQuerySetTest({querySet,queries,test,execute:async(_query,request)=>execute(request)}) as Record<string,unknown>;
report=await executeContextQuerySetTest({querySet,queries,test,execute:(query,request)=>this.executeQuerySources(teamId,projectId,query,request)}) as Record<string,unknown>;
definition={kind:'query-set',id:test.querySetRef!.id,revision:test.querySetRef!.revision,commit:exactRef};
}
const checkedAt=String(report.checkedAt??new Date().toISOString()); const fresh=Math.min(604_800,Math.max(300,Number(input.freshForSeconds??DEFAULT_FRESH_SECONDS)));
Expand Down
Loading
Loading