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
2 changes: 1 addition & 1 deletion backend/consts/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ def _parse_otlp_headers(headers_str: str) -> dict:


# APP Version
APP_VERSION = "v2.2.0"
APP_VERSION = "v2.2.1"


# Skill Creation Streaming Configuration
Expand Down
2 changes: 1 addition & 1 deletion backend/services/conversation_management_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ def save_conversation_assistant(request: AgentRequest, messages: List[str], user
message_list.append(message)

conversation_req = MessageRequest(conversation_id=request.conversation_id, message_idx=user_role_count * 2 + 1,
role=MESSAGE_ROLE["ASSISTANT"], message=message_list, minio_files=request.minio_files)
role=MESSAGE_ROLE["ASSISTANT"], message=message_list, minio_files=None)
save_message(conversation_req, user_id=user_id, tenant_id=tenant_id)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ export default function AgentGenerateDetail({}) {
constraintPrompt: editedAgent.constraint_prompt || "",
fewShotsPrompt: editedAgent.few_shots_prompt || "",
provideRunSummary: editedAgent.provide_run_summary || false,
verificationEnabled: editedAgent.verification_config?.enabled ?? true,
verificationEnabled: editedAgent.verification_config?.enabled ?? false,
businessDescription: editedAgent.business_description || "",
businessLogicModelName:editedAgent.business_logic_model_name,
businessLogicModelId: editedAgent.business_logic_model_id,
Expand Down Expand Up @@ -809,7 +809,7 @@ export default function AgentGenerateDetail({}) {
</Can>

<Row gutter={16}>
<Col span={8}>
<Col span={12}>
<Form.Item
name="agentAuthor"
label={t("agent.author")}
Expand All @@ -828,7 +828,7 @@ export default function AgentGenerateDetail({}) {
/>
</Form.Item>
</Col>
<Col span={8}>
<Col span={12}>
<Form.Item
name="mainAgentModel"
label={t("businessLogic.config.model")}
Expand Down Expand Up @@ -875,7 +875,7 @@ export default function AgentGenerateDetail({}) {
</Row>

<Row gutter={16}>
<Col span={12}>
<Col span={8}>
<Form.Item
name="mainAgentMaxStep"
label={t("businessLogic.config.maxSteps")}
Expand Down Expand Up @@ -903,7 +903,7 @@ export default function AgentGenerateDetail({}) {
/>
</Form.Item>
</Col>
<Col span={12}>
<Col span={8}>
<Form.Item
name="provideRunSummary"
label={t("agent.provideRunSummary")}
Expand Down
1 change: 0 additions & 1 deletion frontend/components/agent/AgentImportWizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,6 @@ export default function AgentImportWizard({
items: agentsWithConflicts.map(([agentKey, conflict]) => {
const agentInfo = initialData.agent_info[agentKey] as any;
return {
agent_id: agentInfo?.agent_id,
name: conflict.renamedName || agentInfo?.name || "",
display_name: conflict.renamedDisplayName || agentInfo?.display_name || "",
task_description: agentInfo?.business_description || agentInfo?.description || "",
Expand Down
2 changes: 1 addition & 1 deletion k8s/helm/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,7 @@ apply() {
sleep 5
for svc in $backend_services; do
echo " Waiting for nexent-$svc..."
if kubectl wait --for=condition=ready pod -l app=nexent-$svc -n $NAMESPACE --timeout=300s 2>/dev/null; then
if kubectl rollout status "deployment/nexent-$svc" -n "$NAMESPACE" --timeout=300s >/dev/null 2>&1; then
echo " nexent-$svc is ready."
else
echo " Error: nexent-$svc did not become ready within timeout."
Expand Down
207 changes: 207 additions & 0 deletions k8s/helm/nexent/charts/nexent-common/files/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1896,3 +1896,210 @@
COMMENT ON COLUMN nexent.user_cas_session_t.session_id IS 'JWT sid claim for revocation checks';
COMMENT ON COLUMN nexent.user_cas_session_t.cas_user_id IS 'User identifier returned by CAS';
COMMENT ON COLUMN nexent.user_cas_session_t.cas_session_index IS 'CAS SessionIndex or service ticket';

-- Rename params -> config_values, add config_schemas to ag_skill_info_t
-- Add tenant_id column for multi-tenancy support
ALTER TABLE nexent.ag_skill_info_t ADD COLUMN IF NOT EXISTS tenant_id VARCHAR(100);

-- Add config_values and config_schemas to ag_skill_info_t
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'nexent'
AND table_name = 'ag_skill_info_t'
AND column_name = 'params'
) THEN
ALTER TABLE nexent.ag_skill_info_t RENAME COLUMN params TO config_values;
END IF;
END $$;
ALTER TABLE nexent.ag_skill_info_t ADD COLUMN IF NOT EXISTS config_schemas JSON;

-- Comments for ag_skill_info_t columns
COMMENT ON COLUMN nexent.ag_skill_info_t.tenant_id IS 'Tenant ID for multi-tenancy. NULL for pre-existing skills.';
COMMENT ON COLUMN nexent.ag_skill_info_t.config_values IS 'Runtime parameter values from config/config.yaml';
COMMENT ON COLUMN nexent.ag_skill_info_t.config_schemas IS 'Parameter metadata list from config/schema.yaml';

-- Add config_values and config_schemas to ag_skill_instance_t
ALTER TABLE nexent.ag_skill_instance_t ADD COLUMN IF NOT EXISTS config_values JSON;
ALTER TABLE nexent.ag_skill_instance_t ADD COLUMN IF NOT EXISTS config_schemas JSON;

-- Comments for ag_skill_instance_t columns
COMMENT ON COLUMN nexent.ag_skill_instance_t.config_values IS 'Per-agent runtime parameter values from config/config.yaml';
COMMENT ON COLUMN nexent.ag_skill_instance_t.config_schemas IS 'Per-agent parameter schema overrides from config/schema.yaml';

-- Migration: ASSET_OWNER role permissions and invitation type comment
-- Date: 2026-05-29
-- Description: Add ASSET_OWNER role permissions, SU asset-owner invite permissions,
-- update invitation code_type comment, and ensure ag_skill_info_t.tenant_id exists
-- Source: commit 15cece97692db2372a978cbdf21b5d5316e79f30 (init.sql)

SET search_path TO nexent;

BEGIN;

COMMENT ON COLUMN nexent.tenant_invitation_code_t.code_type IS
'Invitation code type: ADMIN_INVITE, DEV_INVITE, USER_INVITE, ASSET_OWNER_INVITE';

INSERT INTO nexent.role_permission_t
(role_permission_id, user_role, permission_category, permission_type, permission_subtype)
VALUES
(188, 'SU', 'RESOURCE', 'INVITE.ASSET_OWNER', 'CREATE'),

Check failure on line 1947 in k8s/helm/nexent/charts/nexent-common/files/init.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal 4 times.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ7Z6i8yi6hIn2mDQrF1&open=AZ7Z6i8yi6hIn2mDQrF1&pullRequest=3270
(189, 'SU', 'RESOURCE', 'INVITE.ASSET_OWNER', 'READ'),
(190, 'SU', 'RESOURCE', 'INVITE.ASSET_OWNER', 'UPDATE'),
(191, 'SU', 'RESOURCE', 'INVITE.ASSET_OWNER', 'DELETE'),
(192, 'ASSET_OWNER', 'VISIBILITY', 'LEFT_NAV_MENU', '/'),

Check failure on line 1951 in k8s/helm/nexent/charts/nexent-common/files/init.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal 29 times.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ7Z6i8yi6hIn2mDQrF0&open=AZ7Z6i8yi6hIn2mDQrF0&pullRequest=3270
(193, 'ASSET_OWNER', 'VISIBILITY', 'LEFT_NAV_MENU', '/agents'),
(194, 'ASSET_OWNER', 'VISIBILITY', 'LEFT_NAV_MENU', '/knowledges'),
(195, 'ASSET_OWNER', 'VISIBILITY', 'LEFT_NAV_MENU', '/chat'),
(196, 'ASSET_OWNER', 'VISIBILITY', 'LEFT_NAV_MENU', '/space'),
(197, 'ASSET_OWNER', 'VISIBILITY', 'LEFT_NAV_MENU', '/market'),
(198, 'ASSET_OWNER', 'VISIBILITY', 'LEFT_NAV_MENU', '/models'),
(199, 'ASSET_OWNER', 'RESOURCE', 'AGENT', 'CREATE'),
(200, 'ASSET_OWNER', 'RESOURCE', 'AGENT', 'READ'),
(201, 'ASSET_OWNER', 'RESOURCE', 'AGENT', 'UPDATE'),
(202, 'ASSET_OWNER', 'RESOURCE', 'AGENT', 'DELETE'),
(203, 'ASSET_OWNER', 'RESOURCE', 'SKILL', 'CREATE'),

Check failure on line 1962 in k8s/helm/nexent/charts/nexent-common/files/init.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal 4 times.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ7Z6i8yi6hIn2mDQrF2&open=AZ7Z6i8yi6hIn2mDQrF2&pullRequest=3270
(204, 'ASSET_OWNER', 'RESOURCE', 'SKILL', 'READ'),
(205, 'ASSET_OWNER', 'RESOURCE', 'SKILL', 'UPDATE'),
(206, 'ASSET_OWNER', 'RESOURCE', 'SKILL', 'DELETE'),
(207, 'ASSET_OWNER', 'RESOURCE', 'KB', 'CREATE'),
(208, 'ASSET_OWNER', 'RESOURCE', 'KB', 'READ'),
(209, 'ASSET_OWNER', 'RESOURCE', 'KB', 'UPDATE'),
(210, 'ASSET_OWNER', 'RESOURCE', 'KB', 'DELETE'),
(211, 'ASSET_OWNER', 'RESOURCE', 'MCP', 'CREATE'),
(212, 'ASSET_OWNER', 'RESOURCE', 'MCP', 'READ'),
(213, 'ASSET_OWNER', 'RESOURCE', 'MCP', 'UPDATE'),
(214, 'ASSET_OWNER', 'RESOURCE', 'MCP', 'DELETE'),
(215, 'ASSET_OWNER', 'RESOURCE', 'MODEL', 'CREATE'),
(216, 'ASSET_OWNER', 'RESOURCE', 'MODEL', 'READ'),
(217, 'ASSET_OWNER', 'RESOURCE', 'MODEL', 'UPDATE'),
(218, 'ASSET_OWNER', 'RESOURCE', 'MODEL', 'DELETE'),
(219, 'ASSET_OWNER', 'RESOURCE', 'USER.ROLE', 'READ'),
(220, 'ASSET_OWNER', 'VISIBILITY', 'LEFT_NAV_MENU', '/users'),
(221, 'SU', 'VISIBILITY', 'LEFT_NAV_MENU', '/asset-owner-resources')
ON CONFLICT (role_permission_id) DO NOTHING;

COMMIT;

-- Migration: Add preserve_source_file to knowledge_record_t table
-- Date: 2026-06-01
-- Description: Whether to preserve uploaded source documents after vectorization (default: true)

ALTER TABLE nexent.knowledge_record_t
ADD COLUMN IF NOT EXISTS preserve_source_file BOOLEAN NOT NULL DEFAULT true;

COMMENT ON COLUMN nexent.knowledge_record_t.preserve_source_file IS 'Whether to preserve uploaded source documents after vectorization';

-- Migration: Add ag_agent_repository_t table
-- Date: 2026-06-05
-- Description: Agent marketplace repository for frozen shareable agent snapshots.

SET search_path TO nexent;

BEGIN;

CREATE SEQUENCE IF NOT EXISTS nexent.ag_agent_repository_t_agent_repository_id_seq;

CREATE TABLE IF NOT EXISTS nexent.ag_agent_repository_t (
agent_repository_id BIGINT NOT NULL DEFAULT nextval('nexent.ag_agent_repository_t_agent_repository_id_seq'),
publisher_tenant_id VARCHAR(100) NOT NULL,
publisher_user_id VARCHAR(100) NOT NULL,
agent_id INTEGER NOT NULL,
source_version_no INTEGER NOT NULL,
name VARCHAR(100) NOT NULL,
display_name VARCHAR(100),
description TEXT,
author VARCHAR(100),
category_id INTEGER,
tags TEXT[],
tool_count INTEGER,
version_label VARCHAR(100),
agent_info_json JSONB NOT NULL,
status VARCHAR(30) DEFAULT 'NOT_SHARED',
create_time TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
created_by VARCHAR(100),
updated_by VARCHAR(100),
delete_flag VARCHAR(1) DEFAULT 'N',
CONSTRAINT ag_agent_repository_t_pkey PRIMARY KEY (agent_repository_id)
);

ALTER SEQUENCE nexent.ag_agent_repository_t_agent_repository_id_seq
OWNED BY nexent.ag_agent_repository_t.agent_repository_id;

ALTER TABLE nexent.ag_agent_repository_t OWNER TO root;

COMMENT ON TABLE nexent.ag_agent_repository_t IS 'Agent marketplace repository for frozen shareable agent snapshots';
COMMENT ON COLUMN nexent.ag_agent_repository_t.agent_repository_id IS 'Agent repository listing ID, unique primary key';
COMMENT ON COLUMN nexent.ag_agent_repository_t.publisher_tenant_id IS 'Publisher tenant ID';
COMMENT ON COLUMN nexent.ag_agent_repository_t.publisher_user_id IS 'Publisher user ID';
COMMENT ON COLUMN nexent.ag_agent_repository_t.agent_id IS 'Root agent ID from ag_tenant_agent_t; upsert key with publisher_tenant_id';
COMMENT ON COLUMN nexent.ag_agent_repository_t.source_version_no IS 'Published version number frozen at share time';
COMMENT ON COLUMN nexent.ag_agent_repository_t.name IS 'Root agent programmatic name for display and search';
COMMENT ON COLUMN nexent.ag_agent_repository_t.display_name IS 'Root agent display name';
COMMENT ON COLUMN nexent.ag_agent_repository_t.description IS 'Root agent description';
COMMENT ON COLUMN nexent.ag_agent_repository_t.author IS 'Agent author';
COMMENT ON COLUMN nexent.ag_agent_repository_t.category_id IS 'Optional marketplace category ID';
COMMENT ON COLUMN nexent.ag_agent_repository_t.tags IS 'Marketplace tags';
COMMENT ON COLUMN nexent.ag_agent_repository_t.tool_count IS 'Total tool count across all agents in the bundle (display only)';
COMMENT ON COLUMN nexent.ag_agent_repository_t.version_label IS 'Repository entry version label for display (e.g. v1.0)';
COMMENT ON COLUMN nexent.ag_agent_repository_t.agent_info_json IS 'Frozen ExportAndImportDataFormat snapshot with optional skills';
COMMENT ON COLUMN nexent.ag_agent_repository_t.status IS 'Listing status: NOT_SHARED (未共享) / PENDING_REVIEW (待审核) / REJECTED (审核驳回) / SHARED (已共享)';
COMMENT ON COLUMN nexent.ag_agent_repository_t.create_time IS 'Creation time';
COMMENT ON COLUMN nexent.ag_agent_repository_t.update_time IS 'Update time';
COMMENT ON COLUMN nexent.ag_agent_repository_t.created_by IS 'Creator ID';
COMMENT ON COLUMN nexent.ag_agent_repository_t.updated_by IS 'Updater ID';
COMMENT ON COLUMN nexent.ag_agent_repository_t.delete_flag IS 'Soft delete flag: Y/N';

CREATE UNIQUE INDEX IF NOT EXISTS uq_agent_repository_tenant_agent_active
ON nexent.ag_agent_repository_t (publisher_tenant_id, agent_id)
WHERE delete_flag = 'N';

CREATE INDEX IF NOT EXISTS idx_agent_repository_publisher_delete
ON nexent.ag_agent_repository_t (publisher_tenant_id, delete_flag);

CREATE INDEX IF NOT EXISTS idx_agent_repository_status_delete
ON nexent.ag_agent_repository_t (status, delete_flag);

CREATE INDEX IF NOT EXISTS idx_agent_repository_name_delete
ON nexent.ag_agent_repository_t (name, delete_flag);

CREATE INDEX IF NOT EXISTS idx_agent_repository_tags_gin
ON nexent.ag_agent_repository_t USING GIN (tags);

CREATE OR REPLACE FUNCTION update_ag_agent_repository_update_time()
RETURNS TRIGGER AS $$
BEGIN
NEW.update_time = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

COMMENT ON FUNCTION update_ag_agent_repository_update_time() IS 'Auto-update update_time for ag_agent_repository_t';

DROP TRIGGER IF EXISTS update_ag_agent_repository_update_time_trigger ON nexent.ag_agent_repository_t;
CREATE TRIGGER update_ag_agent_repository_update_time_trigger
BEFORE UPDATE ON nexent.ag_agent_repository_t
FOR EACH ROW
EXECUTE FUNCTION update_ag_agent_repository_update_time();

COMMENT ON TRIGGER update_ag_agent_repository_update_time_trigger ON nexent.ag_agent_repository_t IS 'Trigger to maintain update_time';

COMMIT;

-- Migration: Add selected_agent_version_no to ag_agent_relation_t
-- Date: 2026-06-09
-- Description: Pin child agent version on parent-child relations at publish time.

SET search_path TO nexent;

BEGIN;

ALTER TABLE nexent.ag_agent_relation_t
ADD COLUMN IF NOT EXISTS selected_agent_version_no INTEGER;

COMMENT ON COLUMN nexent.ag_agent_relation_t.selected_agent_version_no IS
'Pinned version of selected_agent_id. NULL = use child current published version at runtime (legacy/draft).';

COMMIT;
2 changes: 1 addition & 1 deletion k8s/helm/nexent/charts/nexent-data-process/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ resources:
memory: 512Mi
cpu: 0.5
limits:
memory: 4Gi
memory: 64Gi
cpu: 8

config:
Expand Down
5 changes: 0 additions & 5 deletions scripts/deployment/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -319,11 +319,6 @@ deployment_validate() {
deployment_error "Local config schemaVersion $DEPLOYMENT_LOADED_SCHEMA_VERSION is incompatible with $DEPLOYMENT_SCHEMA_VERSION. Re-run with --reconfigure."
return 1
fi
if [ -n "$DEPLOYMENT_LOADED_APP_VERSION" ] && [ -n "${APP_VERSION:-}" ] && [ -z "${DEPLOYMENT_APP_VERSION_EXPLICIT:-}" ] && [ "$DEPLOYMENT_LOADED_APP_VERSION" != "$APP_VERSION" ]; then
deployment_error "Local config appVersion $DEPLOYMENT_LOADED_APP_VERSION does not match current appVersion $APP_VERSION. Re-run with --reconfigure or pass --app-version."
return 1
fi

local old_ifs="$IFS"
local component
IFS=','
Expand Down
50 changes: 46 additions & 4 deletions sdk/nexent/container/k8s_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import asyncio
import logging
import socket
import re
import uuid

import kubernetes
Expand All @@ -23,6 +24,47 @@

logger = logging.getLogger("nexent.container.kubernetes")

# Kubernetes naming constraints: lowercase alphanumeric or dash, cannot start/end with dash,
# cannot have consecutive dashes, max 253 characters
K8S_NAME_PATTERN = re.compile(r"[^a-z0-9-]+")
K8S_CONSECUTIVE_DASHES = re.compile(r"-+")


def _sanitize_k8s_name(name: str) -> str:
"""Convert arbitrary string to valid Kubernetes resource name.

Rules:
- Convert to lowercase
- Replace invalid characters with dash
- Collapse consecutive dashes
- Remove leading/trailing dashes
- Must start with alphanumeric

Args:
name: Input string to sanitize

Returns:
Valid Kubernetes name (lowercase alphanumeric and dashes only)
"""
if not name:
return "unknown"

# Lowercase and replace invalid chars with dash
sanitized = K8S_NAME_PATTERN.sub("-", name.lower())

# Collapse consecutive dashes
sanitized = K8S_CONSECUTIVE_DASHES.sub("-", sanitized)

# Remove leading/trailing dashes
sanitized = sanitized.strip("-")

# Ensure it starts with alphanumeric
if sanitized and not sanitized[0].isalnum():
sanitized = "x" + sanitized

# Fallback if empty
return sanitized if sanitized else "unknown"


class ContainerError(Exception):
"""Raised when container operation fails"""
Expand Down Expand Up @@ -77,9 +119,9 @@ def __init__(self, config: KubernetesContainerConfig):

def _generate_pod_name(self, service_name: str, tenant_id: str, user_id: str) -> str:
"""Generate unique pod name with service, tenant, and user segments."""
safe_name = "".join(c if c.isalnum() or c == "-" else "-" for c in service_name)
tenant_part = (tenant_id or "")[:8]
user_part = (user_id or "")[:8]
safe_name = _sanitize_k8s_name(service_name)
tenant_part = _sanitize_k8s_name(tenant_id)[:8]
user_part = _sanitize_k8s_name(user_id)[:8]
uuid_part = uuid.uuid4().hex[:8]
return f"mcp-{safe_name}-{tenant_part}-{user_part}-{uuid_part}"

Expand Down Expand Up @@ -486,7 +528,7 @@ def list_containers(

# Filter by service_name if provided
if service_name:
safe_name = "".join(c if c.isalnum() or c == "-" else "-" for c in service_name)
safe_name = _sanitize_k8s_name(service_name)
pod_component = labels.get(self.LABEL_COMPONENT, "")
if safe_name not in pod_component:
continue
Expand Down
Loading
Loading