mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-22 08:25:21 +08:00
feat(runner): unify plugin execution across agents and event processors
This commit is contained in:
@@ -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')
|
||||
+23
-21
@@ -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'])
|
||||
Reference in New Issue
Block a user