feat(runner): unify plugin execution across agents and event processors

This commit is contained in:
RockChinQ
2026-09-10 18:04:38 +08:00
parent 8903a40c41
commit f24a7c9bb2
223 changed files with 4090 additions and 3067 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ from langbot.pkg.entity.persistence import (
agent, # noqa: F401
agent_interaction, # noqa: F401
agent_run, # noqa: F401
agent_runner_state, # noqa: F401
runner_state, # noqa: F401
apikey, # noqa: F401
bot, # noqa: F401
bstorage, # noqa: F401
@@ -1,4 +1,4 @@
"""Migrate official AgentRunner IDs to their marketplace identities.
"""Migrate official Runner IDs to their marketplace identities.
Revision ID: 0015_official_runner_ids
Revises: 0014_interaction_delivery
@@ -21,7 +21,7 @@ depends_on = None
_RUNNER_ID_RENAMES = {
'plugin:langbot/acp-agent-runner/default': 'plugin:langbot-team/ACPAgentRunner/default',
'plugin:langbot/acp-agent-runner/default': 'plugin:langbot-team/ACPRunner/default',
'plugin:langbot/claude-code-agent/default': 'plugin:langbot-team/ClaudeCodeAgent/default',
'plugin:langbot/codex-agent/default': 'plugin:langbot-team/CodexAgent/default',
'plugin:langbot/coze-agent/default': 'plugin:langbot-team/CozeAgent/default',
@@ -186,17 +186,16 @@ def _rewrite_runner_state(renames: dict[str, str]) -> None:
scope_key = _state_scope_key(row, runner_id, binding_identity) or row['scope_key']
collision = bind.execute(
sa.text(
'SELECT id FROM agent_runner_state '
'WHERE scope_key = :scope_key AND state_key = :state_key AND id != :id'
'SELECT id FROM runner_state WHERE scope_key = :scope_key AND state_key = :state_key AND id != :id'
),
{'scope_key': scope_key, 'state_key': row['state_key'], 'id': row['id']},
).scalar_one_or_none()
if collision is not None:
bind.execute(sa.text('DELETE FROM agent_runner_state WHERE id = :id'), {'id': row['id']})
bind.execute(sa.text('DELETE FROM runner_state WHERE id = :id'), {'id': row['id']})
continue
bind.execute(
sa.text(
'UPDATE agent_runner_state '
'UPDATE runner_state '
'SET runner_id = :runner_id, binding_identity = :binding_identity, scope_key = :scope_key '
'WHERE id = :id'
),
@@ -1,4 +1,4 @@
"""Merge AgentRunner and Workspace heads and scope Agents to a Workspace.
"""Merge Runner and Workspace heads and scope Agents to a Workspace.
Revision ID: 0016_agent_workspace
Revises: 0015_official_runner_ids, 0015_cloud_core_collab
@@ -27,10 +27,7 @@ def _inspector(conn: sa.Connection) -> sa.Inspector:
def _columns(conn: sa.Connection) -> dict[str, dict]:
return {
column['name']: column
for column in _inspector(conn).get_columns(_TABLE)
}
return {column['name']: column for column in _inspector(conn).get_columns(_TABLE)}
def _default_workspace_uuid(conn: sa.Connection) -> str | None:
@@ -59,8 +56,7 @@ def _default_workspace_uuid(conn: sa.Connection) -> str | None:
def _foreign_key_exists(conn: sa.Connection) -> bool:
return any(
tuple(foreign_key.get('constrained_columns') or ())
== ('workspace_uuid',)
tuple(foreign_key.get('constrained_columns') or ()) == ('workspace_uuid',)
and foreign_key.get('referred_table') == 'workspaces'
and tuple(foreign_key.get('referred_columns') or ()) == ('uuid',)
for foreign_key in _inspector(conn).get_foreign_keys(_TABLE)
@@ -75,10 +71,7 @@ def _enable_postgres_rls(conn: sa.Connection) -> None:
op.execute(sa.text(f'DROP POLICY IF EXISTS {policy} ON {table}'))
op.execute(sa.text(f'ALTER TABLE {table} ENABLE ROW LEVEL SECURITY'))
op.execute(sa.text(f'ALTER TABLE {table} FORCE ROW LEVEL SECURITY'))
expression = (
"workspace_uuid::text = "
f"NULLIF(current_setting('{_TENANT_SETTING}', true), '')"
)
expression = f"workspace_uuid::text = NULLIF(current_setting('{_TENANT_SETTING}', true), '')"
op.execute(
sa.text(
f'CREATE POLICY {policy} ON {table} AS PERMISSIVE FOR ALL TO PUBLIC '
@@ -104,22 +97,12 @@ def upgrade() -> None:
_TABLE,
sa.column('workspace_uuid', sa.String(36)),
)
null_count = conn.scalar(
sa.select(sa.func.count())
.select_from(agents)
.where(agents.c.workspace_uuid.is_(None))
)
null_count = conn.scalar(sa.select(sa.func.count()).select_from(agents).where(agents.c.workspace_uuid.is_(None)))
if null_count:
workspace_uuid = _default_workspace_uuid(conn)
if workspace_uuid is None:
raise RuntimeError(
'Cannot backfill Agents: the instance has no unique local Workspace'
)
conn.execute(
agents.update()
.where(agents.c.workspace_uuid.is_(None))
.values(workspace_uuid=workspace_uuid)
)
raise RuntimeError('Cannot backfill Agents: the instance has no unique local Workspace')
conn.execute(agents.update().where(agents.c.workspace_uuid.is_(None)).values(workspace_uuid=workspace_uuid))
columns = _columns(conn)
needs_contract = columns['workspace_uuid']['nullable'] or not _foreign_key_exists(conn)
@@ -140,9 +123,7 @@ def upgrade() -> None:
ondelete='CASCADE',
)
index_names = {
index['name'] for index in _inspector(conn).get_indexes(_TABLE)
}
index_names = {index['name'] for index in _inspector(conn).get_indexes(_TABLE)}
if 'ix_agents_workspace_name' not in index_names:
op.create_index(
'ix_agents_workspace_name',
@@ -172,9 +153,7 @@ def downgrade() -> None:
op.execute(sa.text(f'ALTER TABLE {table} NO FORCE ROW LEVEL SECURITY'))
op.execute(sa.text(f'ALTER TABLE {table} DISABLE ROW LEVEL SECURITY'))
index_names = {
index['name'] for index in _inspector(conn).get_indexes(_TABLE)
}
index_names = {index['name'] for index in _inspector(conn).get_indexes(_TABLE)}
for index_name in ('ix_agents_workspace_updated', 'ix_agents_workspace_name'):
if index_name in index_names:
op.drop_index(index_name, table_name=_TABLE)
@@ -184,8 +163,7 @@ def downgrade() -> None:
(
foreign_key
for foreign_key in foreign_keys
if tuple(foreign_key.get('constrained_columns') or ())
== ('workspace_uuid',)
if tuple(foreign_key.get('constrained_columns') or ()) == ('workspace_uuid',)
and foreign_key.get('referred_table') == 'workspaces'
),
None,
@@ -1,4 +1,4 @@
"""merge AgentRunner and OSS Workspace migration heads
"""merge Runner and OSS Workspace migration heads
Revision ID: 0018_merge_workspace_heads
Revises: 0017_local_owner_repair, 0017_oss_workspace_identity
@@ -1,4 +1,4 @@
"""merge AgentRunner and Cloud Workspace migration heads
"""merge Runner and Cloud Workspace migration heads
Revision ID: 0020_merge_agent_cloud_heads
Revises: 0018_merge_workspace_heads, 0019_single_workspace_owner
@@ -1,4 +1,4 @@
"""merge AgentRunner and model reasoning migration heads
"""merge Runner and model reasoning migration heads
Revision ID: 0022_merge_agent_reasoning_heads
Revises: 0020_merge_agent_cloud_heads, 0021_merge_reasoning_config
@@ -0,0 +1,41 @@
"""Rename the persisted Runner state table without discarding development data.
Revision ID: 0024_unify_runner_state
Revises: 0023_drop_agent_enabled
"""
from alembic import op
import sqlalchemy as sa
revision = '0024_unify_runner_state'
down_revision = '0023_drop_agent_enabled'
branch_labels = None
depends_on = None
def _rename(source, target):
tables = sa.inspect(op.get_bind()).get_table_names()
if source in tables:
if target in tables:
metadata = sa.MetaData()
source_table = sa.Table(source, metadata, autoload_with=op.get_bind())
target_table = sa.Table(target, metadata, autoload_with=op.get_bind())
target_count = op.get_bind().scalar(sa.select(sa.func.count()).select_from(target_table))
source_count = op.get_bind().scalar(sa.select(sa.func.count()).select_from(source_table))
if not target_count:
# Startup may already have created the empty current model table.
op.drop_table(target)
elif not source_count:
op.drop_table(source)
return
else:
raise RuntimeError(f'Both {source} and {target} contain state; reconcile before migrating')
op.rename_table(source, target)
def upgrade():
_rename('agent_runner_state', 'runner_state')
def downgrade():
_rename('runner_state', 'agent_runner_state')
@@ -1,10 +1,11 @@
# Alembic script.py.mako — template for auto-generated revisions
"""add agent_runner_state table for host-owned persistent state
"""add runner_state table for host-owned persistent state
Revision ID: 6dfd3dd7f0c7
Revises: 58846a8d7a81
Create Date: 2026-05-23 19:49:08.529110
"""
from alembic import op
import sqlalchemy as sa
@@ -41,26 +42,27 @@ def _drop_index_if_exists(table_name: str, index_name: str) -> None:
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
if not _table_exists('agent_runner_state'):
op.create_table('agent_runner_state',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('runner_id', sa.String(length=255), nullable=False),
sa.Column('binding_identity', sa.String(length=255), nullable=False),
sa.Column('scope', sa.String(length=50), nullable=False),
sa.Column('scope_key', sa.String(length=512), nullable=False),
sa.Column('state_key', sa.String(length=255), nullable=False),
sa.Column('value_json', sa.Text(), nullable=True),
sa.Column('bot_id', sa.String(length=255), nullable=True),
sa.Column('workspace_id', sa.String(length=255), nullable=True),
sa.Column('conversation_id', sa.String(length=255), nullable=True),
sa.Column('thread_id', sa.String(length=255), nullable=True),
sa.Column('actor_type', sa.String(length=50), nullable=True),
sa.Column('actor_id', sa.String(length=255), nullable=True),
sa.Column('subject_type', sa.String(length=50), nullable=True),
sa.Column('subject_id', sa.String(length=255), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('scope_key', 'state_key', name='uq_agent_runner_state_scope_key_state_key')
op.create_table(
'agent_runner_state',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('runner_id', sa.String(length=255), nullable=False),
sa.Column('binding_identity', sa.String(length=255), nullable=False),
sa.Column('scope', sa.String(length=50), nullable=False),
sa.Column('scope_key', sa.String(length=512), nullable=False),
sa.Column('state_key', sa.String(length=255), nullable=False),
sa.Column('value_json', sa.Text(), nullable=True),
sa.Column('bot_id', sa.String(length=255), nullable=True),
sa.Column('workspace_id', sa.String(length=255), nullable=True),
sa.Column('conversation_id', sa.String(length=255), nullable=True),
sa.Column('thread_id', sa.String(length=255), nullable=True),
sa.Column('actor_type', sa.String(length=50), nullable=True),
sa.Column('actor_id', sa.String(length=255), nullable=True),
sa.Column('subject_type', sa.String(length=50), nullable=True),
sa.Column('subject_id', sa.String(length=255), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('scope_key', 'state_key', name='uq_agent_runner_state_scope_key_state_key'),
)
_create_index_if_missing('agent_runner_state', 'ix_agent_runner_state_actor_id', ['actor_id'])
_create_index_if_missing('agent_runner_state', 'ix_agent_runner_state_binding_identity', ['binding_identity'])