@consiliency/agent-board-schema 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +11 -0
- package/README.md +55 -0
- package/bin/agent-board-schema.js +97 -0
- package/fixtures/already-applied.json +181 -0
- package/fixtures/divergent-history.json +173 -0
- package/fixtures/production-history.json +173 -0
- package/migrations/20260501000001_agent_board_schema_version.sql +12 -0
- package/migrations/20260502000000_agent_board_schema.sql +180 -0
- package/migrations/20260503000000_agent_board_rpcs.sql +422 -0
- package/migrations/20260503000001_agent_board_coordination_rpcs.sql +229 -0
- package/migrations/20260504000000_agent_board_read_models.sql +206 -0
- package/migrations/20260505000000_agent_board_inbox_schema.sql +341 -0
- package/migrations/20260506000000_agent_board_inbox_delivery_rpcs.sql +1200 -0
- package/migrations/20260506000001_agent_board_inbox_callback_rpcs.sql +330 -0
- package/migrations/20260507000000_agent_board_inbox_reads.sql +334 -0
- package/migrations/20260508000000_agent_board_callback_runtime.sql +453 -0
- package/migrations/20260508000001_agent_board_inbox_service_role_reads.sql +10 -0
- package/migrations/20260510000000_agent_board_dblint_repairs.sql +413 -0
- package/migrations/20260511000000_agent_board_auth_schema.sql +146 -0
- package/migrations/20260511000001_agent_board_auth_credentials.sql +39 -0
- package/migrations/20260512000000_agent_board_auth_provisioning_rpcs.sql +622 -0
- package/migrations/20260512000001_agent_board_auth_v1_rpcs.sql +798 -0
- package/migrations/20260512000002_agent_board_auth_v2_rpcs.sql +1243 -0
- package/migrations/20260512000003_agent_board_auth_rls.sql +484 -0
- package/migrations/20260513000000_agent_board_backup_ops.sql +34 -0
- package/migrations/20260513000001_agent_board_retention.sql +160 -0
- package/migrations/20260513000002_agent_board_monitoring_views.sql +227 -0
- package/migrations/20260514000000_agent_board_auth_rls_id_qualification.sql +150 -0
- package/migrations/20260515000000_agent_board_boardopt.sql +493 -0
- package/migrations/20260516000000_agent_board_callbackcap.sql +113 -0
- package/migrations/20260517000000_agent_board_addressing.sql +542 -0
- package/migrations/20260628000000_agent_board_signing_store.sql +174 -0
- package/migrations/20260628000001_agent_board_signing_proof_rpcs.sql +215 -0
- package/migrations/20260628000002_agent_board_signature_rls_reads.sql +300 -0
- package/migrations/20260628000003_agent_board_signed_write_gateway.sql +288 -0
- package/migrations/20260628000004_agent_board_fleet_registry.sql +411 -0
- package/migrations/20260628000005_agent_board_fleet_policy.sql +509 -0
- package/migrations/20260628000006_agent_board_fleet_read_models.sql +170 -0
- package/migrations/20260629000100_agent_board_fleet_registry_repair.sql +411 -0
- package/migrations/20260629000101_agent_board_fleet_policy_refresh.sql +509 -0
- package/migrations/20260629000102_agent_board_fleet_read_models_refresh.sql +170 -0
- package/migrations/20260630000100_agent_board_harness_kind_open_vocab.sql +15 -0
- package/migrations/20260710000000_agent_board_authbound_boundary.sql +155 -0
- package/migrations/20260710000100_agent_board_schema_version_rpc.sql +14 -0
- package/migrations/20260710000400_agent_board_nonfleet_identity.sql +87 -0
- package/migrations/20260710000401_agent_board_workload_providers.sql +39 -0
- package/migrations/20260711000000_agent_board_provision_system_fail_closed.sql +242 -0
- package/migrations/20260711000100_canary_signed_fleet_enforcement.sql +234 -0
- package/migrations/20260712000000_agent_board_bundle_endpoint_compat.sql +97 -0
- package/migrations/20260713000000_agent_board_rls_reassert_board_scoping.sql +90 -0
- package/package.json +42 -0
- package/provenance.json +580 -0
- package/schema-manifest.json +458 -0
- package/src/check.js +23 -0
- package/src/index.js +50 -0
- package/src/materialize.js +73 -0
- package/src/plan.js +44 -0
- package/src/tooling.test.mjs +97 -0
- package/tests/agent_board_addressing.test.sql +166 -0
- package/tests/agent_board_auth_rls.test.sql +243 -0
- package/tests/agent_board_auth_rls_id_qualification.test.sql +225 -0
- package/tests/agent_board_auth_rpcs.test.sql +311 -0
- package/tests/agent_board_auth_schema.test.sql +411 -0
- package/tests/agent_board_authbound_boundary.test.sql +148 -0
- package/tests/agent_board_boardopt.test.sql +170 -0
- package/tests/agent_board_bundle_endpoint_compat.test.sql +49 -0
- package/tests/agent_board_callback_runtime.test.sql +425 -0
- package/tests/agent_board_callbackcap.test.sql +222 -0
- package/tests/agent_board_canary_signed_fleet_enforcement.test.sql +60 -0
- package/tests/agent_board_dblint_rpcs.test.sql +247 -0
- package/tests/agent_board_fleet_policy.test.sql +370 -0
- package/tests/agent_board_fleet_read_models.test.sql +158 -0
- package/tests/agent_board_fleet_registry.test.sql +149 -0
- package/tests/agent_board_inbox_reads.test.sql +471 -0
- package/tests/agent_board_inbox_rpcs.test.sql +452 -0
- package/tests/agent_board_inbox_schema.test.sql +355 -0
- package/tests/agent_board_nonfleet_identity.test.sql +26 -0
- package/tests/agent_board_rls_reassert_board_scoping.test.sql +39 -0
- package/tests/agent_board_schema_version.test.sql +24 -0
- package/tests/agent_board_schema_version_rpc.test.sql +40 -0
- package/tests/agent_board_signature_rls_reads.test.sql +133 -0
- package/tests/agent_board_signed_write_gateway.test.sql +219 -0
- package/tests/agent_board_signing_proof_rpcs.test.sql +143 -0
- package/tests/agent_board_signing_store.test.sql +183 -0
- package/tests/agent_board_smoke.test.sql +12 -0
- package/tests/agent_board_v1_invariants.test.sql +298 -0
- package/tests/agent_board_v2_invariants.test.sql +753 -0
- package/tests/agent_board_workload_providers.test.sql +15 -0
- package/tests/fixtures/adopter-base.sql +19 -0
- package/tests/fixtures/operator-hardening.sql +27 -0
- package/tests/fixtures/test-prereqs.sql +84 -0
|
@@ -0,0 +1,798 @@
|
|
|
1
|
+
create or replace function agent_board.register_agent(
|
|
2
|
+
p_agent_type text,
|
|
3
|
+
p_handle text,
|
|
4
|
+
p_display_name text,
|
|
5
|
+
p_status text default 'active',
|
|
6
|
+
p_metadata jsonb default '{}'::jsonb
|
|
7
|
+
)
|
|
8
|
+
returns table(agent_id uuid, handle text, status text, created_at timestamptz, updated_at timestamptz)
|
|
9
|
+
language plpgsql
|
|
10
|
+
security definer
|
|
11
|
+
set search_path = agent_board, public
|
|
12
|
+
as $$
|
|
13
|
+
declare
|
|
14
|
+
v_actor_agent_id uuid;
|
|
15
|
+
v_existing agent_board.agents%rowtype;
|
|
16
|
+
begin
|
|
17
|
+
if nullif(trim(p_handle), '') is null or nullif(trim(p_display_name), '') is null then
|
|
18
|
+
raise exception 'handle and display name are required' using errcode = '22023';
|
|
19
|
+
end if;
|
|
20
|
+
|
|
21
|
+
if agent_board._is_service_role() then
|
|
22
|
+
return query
|
|
23
|
+
insert into agent_board.agents as a(agent_type, handle, display_name, status, metadata)
|
|
24
|
+
values (p_agent_type, trim(p_handle), trim(p_display_name), coalesce(p_status, 'active'), coalesce(p_metadata, '{}'::jsonb))
|
|
25
|
+
on conflict on constraint agents_handle_key do update
|
|
26
|
+
set agent_type = excluded.agent_type,
|
|
27
|
+
display_name = excluded.display_name,
|
|
28
|
+
status = excluded.status,
|
|
29
|
+
metadata = excluded.metadata,
|
|
30
|
+
updated_at = now()
|
|
31
|
+
returning a.id, a.handle, a.status, a.created_at, a.updated_at;
|
|
32
|
+
return;
|
|
33
|
+
end if;
|
|
34
|
+
|
|
35
|
+
v_actor_agent_id := agent_board._resolved_actor_agent_id(null, 'current_agent_id');
|
|
36
|
+
|
|
37
|
+
select *
|
|
38
|
+
into v_existing
|
|
39
|
+
from agent_board.agents
|
|
40
|
+
where id = v_actor_agent_id
|
|
41
|
+
limit 1
|
|
42
|
+
for update;
|
|
43
|
+
|
|
44
|
+
if v_existing.id is null then
|
|
45
|
+
raise exception 'agent not found' using errcode = 'P0002';
|
|
46
|
+
end if;
|
|
47
|
+
|
|
48
|
+
if p_agent_type is distinct from v_existing.agent_type then
|
|
49
|
+
raise exception 'register_agent may not change agent_type after provisioning' using errcode = '42501';
|
|
50
|
+
end if;
|
|
51
|
+
|
|
52
|
+
return query
|
|
53
|
+
update agent_board.agents a
|
|
54
|
+
set handle = trim(p_handle),
|
|
55
|
+
display_name = trim(p_display_name),
|
|
56
|
+
status = coalesce(p_status, a.status),
|
|
57
|
+
metadata = coalesce(a.metadata, '{}'::jsonb) || coalesce(p_metadata, '{}'::jsonb),
|
|
58
|
+
updated_at = now()
|
|
59
|
+
where a.id = v_actor_agent_id
|
|
60
|
+
returning a.id, a.handle, a.status, a.created_at, a.updated_at;
|
|
61
|
+
end;
|
|
62
|
+
$$;
|
|
63
|
+
|
|
64
|
+
create or replace function agent_board.heartbeat(
|
|
65
|
+
p_agent_id uuid,
|
|
66
|
+
p_thread_id uuid default null,
|
|
67
|
+
p_availability text default 'ready',
|
|
68
|
+
p_activity_summary text default null,
|
|
69
|
+
p_expires_in interval default interval '5 minutes',
|
|
70
|
+
p_metadata jsonb default '{}'::jsonb
|
|
71
|
+
)
|
|
72
|
+
returns table(agent_id uuid, availability text, thread_id uuid, last_heartbeat_at timestamptz, expires_at timestamptz, updated_at timestamptz)
|
|
73
|
+
language plpgsql
|
|
74
|
+
security definer
|
|
75
|
+
set search_path = agent_board, public
|
|
76
|
+
as $$
|
|
77
|
+
declare
|
|
78
|
+
v_actor_agent_id uuid;
|
|
79
|
+
begin
|
|
80
|
+
v_actor_agent_id := agent_board._resolved_actor_agent_id(p_agent_id, 'p_agent_id');
|
|
81
|
+
|
|
82
|
+
if p_thread_id is not null then
|
|
83
|
+
if not exists (select 1 from agent_board.threads where id = p_thread_id) then
|
|
84
|
+
raise exception 'thread not found' using errcode = 'P0002';
|
|
85
|
+
end if;
|
|
86
|
+
if not agent_board._current_agent_can_access_thread(p_thread_id) then
|
|
87
|
+
raise exception 'thread not accessible to current agent' using errcode = '42501';
|
|
88
|
+
end if;
|
|
89
|
+
end if;
|
|
90
|
+
|
|
91
|
+
return query
|
|
92
|
+
insert into agent_board.presence(agent_id, thread_id, availability, activity_summary, last_heartbeat_at, expires_at, metadata, updated_at)
|
|
93
|
+
values (v_actor_agent_id, p_thread_id, coalesce(p_availability, 'ready'), p_activity_summary, now(), now() + coalesce(p_expires_in, interval '5 minutes'), coalesce(p_metadata, '{}'::jsonb), now())
|
|
94
|
+
on conflict on constraint presence_pkey do update
|
|
95
|
+
set thread_id = excluded.thread_id,
|
|
96
|
+
availability = excluded.availability,
|
|
97
|
+
activity_summary = excluded.activity_summary,
|
|
98
|
+
last_heartbeat_at = excluded.last_heartbeat_at,
|
|
99
|
+
expires_at = excluded.expires_at,
|
|
100
|
+
metadata = excluded.metadata,
|
|
101
|
+
updated_at = excluded.updated_at
|
|
102
|
+
returning presence.agent_id, presence.availability, presence.thread_id, presence.last_heartbeat_at, presence.expires_at, presence.updated_at;
|
|
103
|
+
end;
|
|
104
|
+
$$;
|
|
105
|
+
|
|
106
|
+
create or replace function agent_board.create_thread(
|
|
107
|
+
p_created_by_agent_id uuid,
|
|
108
|
+
p_subject text,
|
|
109
|
+
p_thread_type text default 'coordination',
|
|
110
|
+
p_metadata jsonb default '{}'::jsonb
|
|
111
|
+
)
|
|
112
|
+
returns table(thread_id uuid, thread_type text, subject text, status text, last_message_sequence bigint, created_at timestamptz, updated_at timestamptz)
|
|
113
|
+
language plpgsql
|
|
114
|
+
security definer
|
|
115
|
+
set search_path = agent_board, public
|
|
116
|
+
as $$
|
|
117
|
+
declare
|
|
118
|
+
v_actor_agent_id uuid;
|
|
119
|
+
begin
|
|
120
|
+
v_actor_agent_id := agent_board._resolved_actor_agent_id(p_created_by_agent_id, 'p_created_by_agent_id');
|
|
121
|
+
|
|
122
|
+
if nullif(trim(p_subject), '') is null then
|
|
123
|
+
raise exception 'subject is required' using errcode = '22023';
|
|
124
|
+
end if;
|
|
125
|
+
|
|
126
|
+
return query
|
|
127
|
+
insert into agent_board.threads(created_by_agent_id, subject, thread_type, metadata)
|
|
128
|
+
values (v_actor_agent_id, trim(p_subject), coalesce(p_thread_type, 'coordination'), coalesce(p_metadata, '{}'::jsonb))
|
|
129
|
+
returning id, threads.thread_type, threads.subject, threads.status, threads.last_message_sequence, threads.created_at, threads.updated_at;
|
|
130
|
+
end;
|
|
131
|
+
$$;
|
|
132
|
+
|
|
133
|
+
create or replace function agent_board.post_message(
|
|
134
|
+
p_thread_id uuid,
|
|
135
|
+
p_author_agent_id uuid,
|
|
136
|
+
p_body text,
|
|
137
|
+
p_message_type text default 'comment',
|
|
138
|
+
p_task_id uuid default null,
|
|
139
|
+
p_metadata jsonb default '{}'::jsonb
|
|
140
|
+
)
|
|
141
|
+
returns table(message_id uuid, thread_id uuid, task_id uuid, message_sequence bigint, created_at timestamptz)
|
|
142
|
+
language plpgsql
|
|
143
|
+
security definer
|
|
144
|
+
set search_path = agent_board, public
|
|
145
|
+
as $$
|
|
146
|
+
declare
|
|
147
|
+
v_sequence bigint;
|
|
148
|
+
v_actor_agent_id uuid;
|
|
149
|
+
begin
|
|
150
|
+
v_actor_agent_id := agent_board._resolved_actor_agent_id(p_author_agent_id, 'p_author_agent_id');
|
|
151
|
+
|
|
152
|
+
if nullif(trim(p_body), '') is null then
|
|
153
|
+
raise exception 'message body is required' using errcode = '22023';
|
|
154
|
+
end if;
|
|
155
|
+
if not exists (select 1 from agent_board.threads where id = p_thread_id) then
|
|
156
|
+
raise exception 'thread not found' using errcode = 'P0002';
|
|
157
|
+
end if;
|
|
158
|
+
if not agent_board._current_agent_can_access_thread(p_thread_id) then
|
|
159
|
+
raise exception 'thread not accessible to current agent' using errcode = '42501';
|
|
160
|
+
end if;
|
|
161
|
+
if p_task_id is not null and not exists (select 1 from agent_board.tasks where id = p_task_id and tasks.thread_id = p_thread_id) then
|
|
162
|
+
raise exception 'task not found in thread' using errcode = '22023';
|
|
163
|
+
end if;
|
|
164
|
+
|
|
165
|
+
v_sequence := agent_board._next_thread_message_sequence(p_thread_id);
|
|
166
|
+
|
|
167
|
+
return query
|
|
168
|
+
insert into agent_board.messages(thread_id, author_agent_id, task_id, message_sequence, message_type, body, metadata)
|
|
169
|
+
values (p_thread_id, v_actor_agent_id, p_task_id, v_sequence, coalesce(p_message_type, 'comment'), p_body, coalesce(p_metadata, '{}'::jsonb))
|
|
170
|
+
returning id, messages.thread_id, messages.task_id, messages.message_sequence, messages.created_at;
|
|
171
|
+
end;
|
|
172
|
+
$$;
|
|
173
|
+
|
|
174
|
+
create or replace function agent_board.create_task(
|
|
175
|
+
p_thread_id uuid,
|
|
176
|
+
p_created_by_agent_id uuid,
|
|
177
|
+
p_title text,
|
|
178
|
+
p_details text default null,
|
|
179
|
+
p_assigned_agent_id uuid default null,
|
|
180
|
+
p_parent_task_id uuid default null,
|
|
181
|
+
p_priority text default 'medium',
|
|
182
|
+
p_orchestration_mode text default 'distributed',
|
|
183
|
+
p_metadata jsonb default '{}'::jsonb
|
|
184
|
+
)
|
|
185
|
+
returns table(task_id uuid, parent_task_id uuid, root_task_id uuid, created_event_id uuid, created_event_sequence bigint, status text, created_at timestamptz, updated_at timestamptz)
|
|
186
|
+
language plpgsql
|
|
187
|
+
security definer
|
|
188
|
+
set search_path = agent_board, public
|
|
189
|
+
as $$
|
|
190
|
+
declare
|
|
191
|
+
v_task_id uuid;
|
|
192
|
+
v_root_task_id uuid;
|
|
193
|
+
v_event_id uuid;
|
|
194
|
+
v_actor_agent_id uuid;
|
|
195
|
+
begin
|
|
196
|
+
v_actor_agent_id := agent_board._resolved_actor_agent_id(p_created_by_agent_id, 'p_created_by_agent_id');
|
|
197
|
+
|
|
198
|
+
if nullif(trim(p_title), '') is null then
|
|
199
|
+
raise exception 'task title is required' using errcode = '22023';
|
|
200
|
+
end if;
|
|
201
|
+
if not exists (select 1 from agent_board.threads where id = p_thread_id) then
|
|
202
|
+
raise exception 'thread not found' using errcode = 'P0002';
|
|
203
|
+
end if;
|
|
204
|
+
if not agent_board._current_agent_can_access_thread(p_thread_id) then
|
|
205
|
+
raise exception 'thread not accessible to current agent' using errcode = '42501';
|
|
206
|
+
end if;
|
|
207
|
+
if p_assigned_agent_id is not null and not exists (select 1 from agent_board.agents where id = p_assigned_agent_id) then
|
|
208
|
+
raise exception 'assigned agent not found' using errcode = 'P0002';
|
|
209
|
+
end if;
|
|
210
|
+
|
|
211
|
+
if p_parent_task_id is not null then
|
|
212
|
+
select coalesce(tasks.root_task_id, tasks.id)
|
|
213
|
+
into v_root_task_id
|
|
214
|
+
from agent_board.tasks
|
|
215
|
+
where id = p_parent_task_id and tasks.thread_id = p_thread_id;
|
|
216
|
+
if v_root_task_id is null then
|
|
217
|
+
raise exception 'parent task not found in thread' using errcode = '22023';
|
|
218
|
+
end if;
|
|
219
|
+
if not agent_board._current_agent_can_access_task(p_parent_task_id) then
|
|
220
|
+
raise exception 'parent task not accessible to current agent' using errcode = '42501';
|
|
221
|
+
end if;
|
|
222
|
+
end if;
|
|
223
|
+
|
|
224
|
+
v_task_id := gen_random_uuid();
|
|
225
|
+
if p_parent_task_id is null then
|
|
226
|
+
v_root_task_id := v_task_id;
|
|
227
|
+
end if;
|
|
228
|
+
|
|
229
|
+
insert into agent_board.tasks(id, thread_id, created_by_agent_id, assigned_agent_id, parent_task_id, root_task_id, title, details, priority, orchestration_mode, metadata)
|
|
230
|
+
values (v_task_id, p_thread_id, v_actor_agent_id, p_assigned_agent_id, p_parent_task_id, v_root_task_id, trim(p_title), p_details, coalesce(p_priority, 'medium'), coalesce(p_orchestration_mode, 'distributed'), coalesce(p_metadata, '{}'::jsonb));
|
|
231
|
+
|
|
232
|
+
insert into agent_board.task_events(task_id, thread_id, actor_agent_id, event_sequence, event_type, payload)
|
|
233
|
+
values (v_task_id, p_thread_id, v_actor_agent_id, 1, 'created', jsonb_build_object('title', trim(p_title)))
|
|
234
|
+
returning id into v_event_id;
|
|
235
|
+
|
|
236
|
+
return query
|
|
237
|
+
select t.id, t.parent_task_id, t.root_task_id, v_event_id, 1::bigint, t.status, t.created_at, t.updated_at
|
|
238
|
+
from agent_board.tasks t
|
|
239
|
+
where t.id = v_task_id;
|
|
240
|
+
end;
|
|
241
|
+
$$;
|
|
242
|
+
|
|
243
|
+
create or replace function agent_board.append_task_event(
|
|
244
|
+
p_task_id uuid,
|
|
245
|
+
p_actor_agent_id uuid,
|
|
246
|
+
p_event_type text,
|
|
247
|
+
p_payload jsonb default '{}'::jsonb,
|
|
248
|
+
p_status text default null,
|
|
249
|
+
p_assigned_agent_id uuid default null,
|
|
250
|
+
p_completed_at timestamptz default null
|
|
251
|
+
)
|
|
252
|
+
returns table(task_id uuid, thread_id uuid, event_id uuid, event_sequence bigint, task_status text, assigned_agent_id uuid, updated_at timestamptz, completed_at timestamptz)
|
|
253
|
+
language plpgsql
|
|
254
|
+
security definer
|
|
255
|
+
set search_path = agent_board, public
|
|
256
|
+
as $$
|
|
257
|
+
declare
|
|
258
|
+
v_thread_id uuid;
|
|
259
|
+
v_sequence bigint;
|
|
260
|
+
v_event_id uuid;
|
|
261
|
+
v_actor_agent_id uuid;
|
|
262
|
+
begin
|
|
263
|
+
v_actor_agent_id := agent_board._resolved_actor_agent_id(p_actor_agent_id, 'p_actor_agent_id');
|
|
264
|
+
|
|
265
|
+
select tasks.thread_id into v_thread_id from agent_board.tasks where id = p_task_id for update;
|
|
266
|
+
if v_thread_id is null then
|
|
267
|
+
raise exception 'task not found' using errcode = 'P0002';
|
|
268
|
+
end if;
|
|
269
|
+
if not agent_board._current_agent_can_access_task(p_task_id) then
|
|
270
|
+
raise exception 'task not accessible to current agent' using errcode = '42501';
|
|
271
|
+
end if;
|
|
272
|
+
|
|
273
|
+
select coalesce(max(te.event_sequence), 0) + 1 into v_sequence from agent_board.task_events te where te.task_id = p_task_id;
|
|
274
|
+
insert into agent_board.task_events(task_id, thread_id, actor_agent_id, event_sequence, event_type, payload)
|
|
275
|
+
values (p_task_id, v_thread_id, v_actor_agent_id, v_sequence, p_event_type, coalesce(p_payload, '{}'::jsonb))
|
|
276
|
+
returning id into v_event_id;
|
|
277
|
+
|
|
278
|
+
update agent_board.tasks
|
|
279
|
+
set status = coalesce(p_status, tasks.status),
|
|
280
|
+
assigned_agent_id = coalesce(p_assigned_agent_id, tasks.assigned_agent_id),
|
|
281
|
+
completed_at = coalesce(p_completed_at, tasks.completed_at),
|
|
282
|
+
updated_at = now()
|
|
283
|
+
where id = p_task_id;
|
|
284
|
+
|
|
285
|
+
return query
|
|
286
|
+
select t.id, t.thread_id, v_event_id, v_sequence, t.status, t.assigned_agent_id, t.updated_at, t.completed_at
|
|
287
|
+
from agent_board.tasks t
|
|
288
|
+
where t.id = p_task_id;
|
|
289
|
+
end;
|
|
290
|
+
$$;
|
|
291
|
+
|
|
292
|
+
create or replace function agent_board.acquire_claim(
|
|
293
|
+
p_agent_id uuid,
|
|
294
|
+
p_resource_kind text,
|
|
295
|
+
p_resource_key text,
|
|
296
|
+
p_lease_expires_at timestamptz,
|
|
297
|
+
p_task_id uuid default null,
|
|
298
|
+
p_thread_id uuid default null,
|
|
299
|
+
p_metadata jsonb default '{}'::jsonb
|
|
300
|
+
)
|
|
301
|
+
returns table(claim_id uuid, task_id uuid, thread_id uuid, agent_id uuid, resource_kind text, resource_key text, status text, lease_token uuid, lease_expires_at timestamptz, released_at timestamptz, created_at timestamptz, updated_at timestamptz)
|
|
302
|
+
language plpgsql
|
|
303
|
+
security definer
|
|
304
|
+
set search_path = agent_board, public
|
|
305
|
+
as $$
|
|
306
|
+
declare
|
|
307
|
+
v_thread_id uuid;
|
|
308
|
+
v_claim_id uuid;
|
|
309
|
+
v_actor_agent_id uuid;
|
|
310
|
+
begin
|
|
311
|
+
v_actor_agent_id := agent_board._resolved_actor_agent_id(p_agent_id, 'p_agent_id');
|
|
312
|
+
|
|
313
|
+
if nullif(trim(p_resource_key), '') is null then
|
|
314
|
+
raise exception 'resource key is required' using errcode = '22023';
|
|
315
|
+
end if;
|
|
316
|
+
|
|
317
|
+
if p_task_id is not null then
|
|
318
|
+
select tasks.thread_id into v_thread_id from agent_board.tasks where id = p_task_id;
|
|
319
|
+
if v_thread_id is null then
|
|
320
|
+
raise exception 'task not found' using errcode = 'P0002';
|
|
321
|
+
end if;
|
|
322
|
+
if not agent_board._current_agent_can_access_task(p_task_id) then
|
|
323
|
+
raise exception 'task not accessible to current agent' using errcode = '42501';
|
|
324
|
+
end if;
|
|
325
|
+
else
|
|
326
|
+
v_thread_id := p_thread_id;
|
|
327
|
+
end if;
|
|
328
|
+
|
|
329
|
+
if v_thread_id is not null and not agent_board._current_agent_can_access_thread(v_thread_id) then
|
|
330
|
+
raise exception 'thread not accessible to current agent' using errcode = '42501';
|
|
331
|
+
end if;
|
|
332
|
+
|
|
333
|
+
update agent_board.claims c
|
|
334
|
+
set status = 'expired',
|
|
335
|
+
updated_at = now()
|
|
336
|
+
where c.resource_kind = p_resource_kind
|
|
337
|
+
and c.resource_key = p_resource_key
|
|
338
|
+
and c.status = 'active'
|
|
339
|
+
and c.released_at is null
|
|
340
|
+
and c.lease_expires_at <= now();
|
|
341
|
+
|
|
342
|
+
if exists (
|
|
343
|
+
select 1 from agent_board.claims
|
|
344
|
+
where claims.resource_kind = p_resource_kind
|
|
345
|
+
and claims.resource_key = p_resource_key
|
|
346
|
+
and claims.status = 'active'
|
|
347
|
+
and claims.released_at is null
|
|
348
|
+
and claims.lease_expires_at > now()
|
|
349
|
+
) then
|
|
350
|
+
raise exception 'conflicting active claim' using errcode = '23505';
|
|
351
|
+
end if;
|
|
352
|
+
|
|
353
|
+
insert into agent_board.claims(task_id, thread_id, agent_id, resource_kind, resource_key, lease_expires_at, metadata)
|
|
354
|
+
values (p_task_id, v_thread_id, v_actor_agent_id, p_resource_kind, p_resource_key, p_lease_expires_at, coalesce(p_metadata, '{}'::jsonb))
|
|
355
|
+
returning id into v_claim_id;
|
|
356
|
+
|
|
357
|
+
if p_task_id is not null then
|
|
358
|
+
perform agent_board.append_task_event(p_task_id, v_actor_agent_id, 'claimed', jsonb_build_object('claim_id', v_claim_id, 'resource_kind', p_resource_kind, 'resource_key', p_resource_key));
|
|
359
|
+
end if;
|
|
360
|
+
|
|
361
|
+
return query
|
|
362
|
+
select c.id, c.task_id, c.thread_id, c.agent_id, c.resource_kind, c.resource_key, c.status, c.lease_token, c.lease_expires_at, c.released_at, c.created_at, c.updated_at
|
|
363
|
+
from agent_board.claims c
|
|
364
|
+
where c.id = v_claim_id;
|
|
365
|
+
end;
|
|
366
|
+
$$;
|
|
367
|
+
|
|
368
|
+
create or replace function agent_board.renew_claim(p_lease_token uuid, p_lease_expires_at timestamptz)
|
|
369
|
+
returns table(claim_id uuid, task_id uuid, thread_id uuid, agent_id uuid, resource_kind text, resource_key text, status text, lease_token uuid, lease_expires_at timestamptz, released_at timestamptz, created_at timestamptz, updated_at timestamptz)
|
|
370
|
+
language plpgsql
|
|
371
|
+
security definer
|
|
372
|
+
set search_path = agent_board, public
|
|
373
|
+
as $$
|
|
374
|
+
declare
|
|
375
|
+
v_actor_agent_id uuid;
|
|
376
|
+
begin
|
|
377
|
+
if not agent_board._is_service_role() then
|
|
378
|
+
v_actor_agent_id := agent_board._resolved_actor_agent_id(null, 'current_agent_id');
|
|
379
|
+
if not exists (
|
|
380
|
+
select 1
|
|
381
|
+
from agent_board.claims c
|
|
382
|
+
where c.lease_token = p_lease_token
|
|
383
|
+
and c.agent_id = v_actor_agent_id
|
|
384
|
+
) then
|
|
385
|
+
raise exception 'claim not accessible to current agent' using errcode = '42501';
|
|
386
|
+
end if;
|
|
387
|
+
end if;
|
|
388
|
+
|
|
389
|
+
return query
|
|
390
|
+
update agent_board.claims c
|
|
391
|
+
set lease_expires_at = p_lease_expires_at,
|
|
392
|
+
updated_at = now()
|
|
393
|
+
where c.lease_token = p_lease_token
|
|
394
|
+
and c.status = 'active'
|
|
395
|
+
and c.released_at is null
|
|
396
|
+
and c.lease_expires_at > now()
|
|
397
|
+
returning c.id, c.task_id, c.thread_id, c.agent_id, c.resource_kind, c.resource_key, c.status, c.lease_token, c.lease_expires_at, c.released_at, c.created_at, c.updated_at;
|
|
398
|
+
end;
|
|
399
|
+
$$;
|
|
400
|
+
|
|
401
|
+
create or replace function agent_board.release_claim(p_lease_token uuid)
|
|
402
|
+
returns table(claim_id uuid, task_id uuid, thread_id uuid, agent_id uuid, resource_kind text, resource_key text, status text, lease_token uuid, lease_expires_at timestamptz, released_at timestamptz, created_at timestamptz, updated_at timestamptz)
|
|
403
|
+
language plpgsql
|
|
404
|
+
security definer
|
|
405
|
+
set search_path = agent_board, public
|
|
406
|
+
as $$
|
|
407
|
+
declare
|
|
408
|
+
v_actor_agent_id uuid;
|
|
409
|
+
begin
|
|
410
|
+
if not agent_board._is_service_role() then
|
|
411
|
+
v_actor_agent_id := agent_board._resolved_actor_agent_id(null, 'current_agent_id');
|
|
412
|
+
if not exists (
|
|
413
|
+
select 1
|
|
414
|
+
from agent_board.claims c
|
|
415
|
+
where c.lease_token = p_lease_token
|
|
416
|
+
and c.agent_id = v_actor_agent_id
|
|
417
|
+
) then
|
|
418
|
+
raise exception 'claim not accessible to current agent' using errcode = '42501';
|
|
419
|
+
end if;
|
|
420
|
+
end if;
|
|
421
|
+
|
|
422
|
+
return query
|
|
423
|
+
update agent_board.claims c
|
|
424
|
+
set status = 'released',
|
|
425
|
+
released_at = now(),
|
|
426
|
+
updated_at = now()
|
|
427
|
+
where c.lease_token = p_lease_token
|
|
428
|
+
and c.status = 'active'
|
|
429
|
+
and c.released_at is null
|
|
430
|
+
returning c.id, c.task_id, c.thread_id, c.agent_id, c.resource_kind, c.resource_key, c.status, c.lease_token, c.lease_expires_at, c.released_at, c.created_at, c.updated_at;
|
|
431
|
+
end;
|
|
432
|
+
$$;
|
|
433
|
+
|
|
434
|
+
create or replace function agent_board.delegate_task(
|
|
435
|
+
p_parent_task_id uuid,
|
|
436
|
+
p_actor_agent_id uuid,
|
|
437
|
+
p_title text,
|
|
438
|
+
p_details text default null,
|
|
439
|
+
p_delegated_to_agent_id uuid default null,
|
|
440
|
+
p_priority text default 'medium',
|
|
441
|
+
p_note text default null,
|
|
442
|
+
p_metadata jsonb default '{}'::jsonb
|
|
443
|
+
)
|
|
444
|
+
returns table(delegation_id uuid, child_task_id uuid, parent_task_id uuid, root_task_id uuid, delegation_status text, child_event_sequence bigint, parent_event_sequence bigint, created_at timestamptz)
|
|
445
|
+
language plpgsql
|
|
446
|
+
security definer
|
|
447
|
+
set search_path = agent_board, public
|
|
448
|
+
as $$
|
|
449
|
+
declare
|
|
450
|
+
v_parent agent_board.tasks%rowtype;
|
|
451
|
+
v_child record;
|
|
452
|
+
v_delegation_id uuid;
|
|
453
|
+
v_parent_event record;
|
|
454
|
+
v_actor_agent_id uuid;
|
|
455
|
+
begin
|
|
456
|
+
v_actor_agent_id := agent_board._resolved_actor_agent_id(p_actor_agent_id, 'p_actor_agent_id');
|
|
457
|
+
|
|
458
|
+
select * into v_parent from agent_board.tasks where id = p_parent_task_id;
|
|
459
|
+
if v_parent.id is null then
|
|
460
|
+
raise exception 'parent task not found' using errcode = 'P0002';
|
|
461
|
+
end if;
|
|
462
|
+
if not agent_board._current_agent_can_access_task(p_parent_task_id) then
|
|
463
|
+
raise exception 'parent task not accessible to current agent' using errcode = '42501';
|
|
464
|
+
end if;
|
|
465
|
+
|
|
466
|
+
select * into v_child
|
|
467
|
+
from agent_board.create_task(v_parent.thread_id, v_actor_agent_id, p_title, p_details, p_delegated_to_agent_id, p_parent_task_id, p_priority, 'hierarchical', p_metadata);
|
|
468
|
+
|
|
469
|
+
insert into agent_board.delegations(parent_task_id, child_task_id, delegated_by_agent_id, delegated_to_agent_id, note, metadata)
|
|
470
|
+
values (p_parent_task_id, v_child.task_id, v_actor_agent_id, p_delegated_to_agent_id, p_note, coalesce(p_metadata, '{}'::jsonb))
|
|
471
|
+
returning id into v_delegation_id;
|
|
472
|
+
|
|
473
|
+
select * into v_parent_event
|
|
474
|
+
from agent_board.append_task_event(p_parent_task_id, v_actor_agent_id, 'delegated', jsonb_build_object('delegation_id', v_delegation_id, 'child_task_id', v_child.task_id));
|
|
475
|
+
|
|
476
|
+
return query
|
|
477
|
+
select v_delegation_id, v_child.task_id, p_parent_task_id, v_child.root_task_id, 'requested'::text, v_child.created_event_sequence, v_parent_event.event_sequence, now();
|
|
478
|
+
end;
|
|
479
|
+
$$;
|
|
480
|
+
|
|
481
|
+
create or replace function agent_board.add_artifact(
|
|
482
|
+
p_created_by_agent_id uuid,
|
|
483
|
+
p_uri text,
|
|
484
|
+
p_artifact_type text default 'url',
|
|
485
|
+
p_storage_kind text default 'external_url',
|
|
486
|
+
p_thread_id uuid default null,
|
|
487
|
+
p_task_id uuid default null,
|
|
488
|
+
p_message_id uuid default null,
|
|
489
|
+
p_task_event_id uuid default null,
|
|
490
|
+
p_metadata jsonb default '{}'::jsonb
|
|
491
|
+
)
|
|
492
|
+
returns table(artifact_id uuid, task_event_sequence bigint, created_at timestamptz)
|
|
493
|
+
language plpgsql
|
|
494
|
+
security definer
|
|
495
|
+
set search_path = agent_board, public
|
|
496
|
+
as $$
|
|
497
|
+
declare
|
|
498
|
+
v_artifact_id uuid;
|
|
499
|
+
v_event record;
|
|
500
|
+
v_actor_agent_id uuid;
|
|
501
|
+
v_thread_id uuid;
|
|
502
|
+
begin
|
|
503
|
+
v_actor_agent_id := agent_board._resolved_actor_agent_id(p_created_by_agent_id, 'p_created_by_agent_id');
|
|
504
|
+
|
|
505
|
+
if p_thread_id is null and p_task_id is null and p_message_id is null and p_task_event_id is null then
|
|
506
|
+
raise exception 'artifact target is required' using errcode = '22023';
|
|
507
|
+
end if;
|
|
508
|
+
|
|
509
|
+
if p_task_id is not null then
|
|
510
|
+
if not agent_board._current_agent_can_access_task(p_task_id) then
|
|
511
|
+
raise exception 'task not accessible to current agent' using errcode = '42501';
|
|
512
|
+
end if;
|
|
513
|
+
end if;
|
|
514
|
+
|
|
515
|
+
if p_thread_id is not null and not agent_board._current_agent_can_access_thread(p_thread_id) then
|
|
516
|
+
raise exception 'thread not accessible to current agent' using errcode = '42501';
|
|
517
|
+
end if;
|
|
518
|
+
|
|
519
|
+
if p_message_id is not null then
|
|
520
|
+
select m.thread_id into v_thread_id from agent_board.messages m where m.id = p_message_id;
|
|
521
|
+
if v_thread_id is null or not agent_board._current_agent_can_access_thread(v_thread_id) then
|
|
522
|
+
raise exception 'message not accessible to current agent' using errcode = '42501';
|
|
523
|
+
end if;
|
|
524
|
+
end if;
|
|
525
|
+
|
|
526
|
+
if p_task_event_id is not null then
|
|
527
|
+
select te.thread_id into v_thread_id from agent_board.task_events te where te.id = p_task_event_id;
|
|
528
|
+
if v_thread_id is null or not agent_board._current_agent_can_access_thread(v_thread_id) then
|
|
529
|
+
raise exception 'task event not accessible to current agent' using errcode = '42501';
|
|
530
|
+
end if;
|
|
531
|
+
end if;
|
|
532
|
+
|
|
533
|
+
insert into agent_board.artifacts(thread_id, task_id, message_id, task_event_id, created_by_agent_id, artifact_type, storage_kind, uri, metadata)
|
|
534
|
+
values (p_thread_id, p_task_id, p_message_id, p_task_event_id, v_actor_agent_id, coalesce(p_artifact_type, 'url'), coalesce(p_storage_kind, 'external_url'), p_uri, coalesce(p_metadata, '{}'::jsonb))
|
|
535
|
+
returning id into v_artifact_id;
|
|
536
|
+
|
|
537
|
+
if p_task_id is not null then
|
|
538
|
+
select * into v_event
|
|
539
|
+
from agent_board.append_task_event(p_task_id, v_actor_agent_id, 'artifact_added', jsonb_build_object('artifact_id', v_artifact_id));
|
|
540
|
+
end if;
|
|
541
|
+
|
|
542
|
+
return query
|
|
543
|
+
select v_artifact_id, v_event.event_sequence, now();
|
|
544
|
+
end;
|
|
545
|
+
$$;
|
|
546
|
+
|
|
547
|
+
create or replace function agent_board.raise_conflict(
|
|
548
|
+
p_thread_id uuid,
|
|
549
|
+
p_raised_by_agent_id uuid,
|
|
550
|
+
p_summary text,
|
|
551
|
+
p_conflict_type text default 'other',
|
|
552
|
+
p_severity text default 'medium',
|
|
553
|
+
p_task_id uuid default null,
|
|
554
|
+
p_details jsonb default '{}'::jsonb
|
|
555
|
+
)
|
|
556
|
+
returns table(conflict_id uuid, status text, task_event_sequence bigint, created_at timestamptz, updated_at timestamptz)
|
|
557
|
+
language plpgsql
|
|
558
|
+
security definer
|
|
559
|
+
set search_path = agent_board, public
|
|
560
|
+
as $$
|
|
561
|
+
declare
|
|
562
|
+
v_conflict_id uuid;
|
|
563
|
+
v_event record;
|
|
564
|
+
v_actor_agent_id uuid;
|
|
565
|
+
begin
|
|
566
|
+
v_actor_agent_id := agent_board._resolved_actor_agent_id(p_raised_by_agent_id, 'p_raised_by_agent_id');
|
|
567
|
+
|
|
568
|
+
if not agent_board._current_agent_can_access_thread(p_thread_id) then
|
|
569
|
+
raise exception 'thread not accessible to current agent' using errcode = '42501';
|
|
570
|
+
end if;
|
|
571
|
+
|
|
572
|
+
insert into agent_board.conflicts(thread_id, task_id, raised_by_agent_id, summary, conflict_type, severity, details)
|
|
573
|
+
values (p_thread_id, p_task_id, v_actor_agent_id, p_summary, coalesce(p_conflict_type, 'other'), coalesce(p_severity, 'medium'), coalesce(p_details, '{}'::jsonb))
|
|
574
|
+
returning id into v_conflict_id;
|
|
575
|
+
|
|
576
|
+
if p_task_id is not null then
|
|
577
|
+
select * into v_event
|
|
578
|
+
from agent_board.append_task_event(p_task_id, v_actor_agent_id, 'conflict_raised', jsonb_build_object('conflict_id', v_conflict_id));
|
|
579
|
+
end if;
|
|
580
|
+
|
|
581
|
+
return query
|
|
582
|
+
select c.id, c.status, v_event.event_sequence, c.created_at, c.updated_at
|
|
583
|
+
from agent_board.conflicts c
|
|
584
|
+
where c.id = v_conflict_id;
|
|
585
|
+
end;
|
|
586
|
+
$$;
|
|
587
|
+
|
|
588
|
+
create or replace function agent_board.resolve_conflict(
|
|
589
|
+
p_conflict_id uuid,
|
|
590
|
+
p_resolved_by_agent_id uuid,
|
|
591
|
+
p_details jsonb default '{}'::jsonb
|
|
592
|
+
)
|
|
593
|
+
returns table(conflict_id uuid, status text, resolved_at timestamptz, updated_at timestamptz, task_event_sequence bigint)
|
|
594
|
+
language plpgsql
|
|
595
|
+
security definer
|
|
596
|
+
set search_path = agent_board, public
|
|
597
|
+
as $$
|
|
598
|
+
declare
|
|
599
|
+
v_conflict agent_board.conflicts%rowtype;
|
|
600
|
+
v_event record;
|
|
601
|
+
v_actor_agent_id uuid;
|
|
602
|
+
begin
|
|
603
|
+
v_actor_agent_id := agent_board._resolved_actor_agent_id(p_resolved_by_agent_id, 'p_resolved_by_agent_id');
|
|
604
|
+
|
|
605
|
+
select *
|
|
606
|
+
into v_conflict
|
|
607
|
+
from agent_board.conflicts c
|
|
608
|
+
where c.id = p_conflict_id
|
|
609
|
+
and c.status <> 'resolved';
|
|
610
|
+
if v_conflict.id is null then
|
|
611
|
+
raise exception 'conflict not found' using errcode = 'P0002';
|
|
612
|
+
end if;
|
|
613
|
+
if not agent_board._current_agent_can_access_thread(v_conflict.thread_id) then
|
|
614
|
+
raise exception 'conflict not accessible to current agent' using errcode = '42501';
|
|
615
|
+
end if;
|
|
616
|
+
|
|
617
|
+
update agent_board.conflicts
|
|
618
|
+
set status = 'resolved',
|
|
619
|
+
resolved_by_agent_id = v_actor_agent_id,
|
|
620
|
+
resolved_at = now(),
|
|
621
|
+
details = details || coalesce(p_details, '{}'::jsonb),
|
|
622
|
+
updated_at = now()
|
|
623
|
+
where id = p_conflict_id
|
|
624
|
+
returning * into v_conflict;
|
|
625
|
+
|
|
626
|
+
if v_conflict.task_id is not null then
|
|
627
|
+
select * into v_event
|
|
628
|
+
from agent_board.append_task_event(v_conflict.task_id, v_actor_agent_id, 'status_changed', jsonb_build_object('kind', 'conflict_resolved', 'conflict_id', p_conflict_id));
|
|
629
|
+
end if;
|
|
630
|
+
|
|
631
|
+
return query
|
|
632
|
+
select v_conflict.id, v_conflict.status, v_conflict.resolved_at, v_conflict.updated_at, v_event.event_sequence;
|
|
633
|
+
end;
|
|
634
|
+
$$;
|
|
635
|
+
|
|
636
|
+
create or replace function agent_board.raise_escalation(
|
|
637
|
+
p_thread_id uuid,
|
|
638
|
+
p_raised_by_agent_id uuid,
|
|
639
|
+
p_summary text,
|
|
640
|
+
p_escalation_type text default 'other',
|
|
641
|
+
p_severity text default 'medium',
|
|
642
|
+
p_task_id uuid default null,
|
|
643
|
+
p_assigned_agent_id uuid default null,
|
|
644
|
+
p_details jsonb default '{}'::jsonb
|
|
645
|
+
)
|
|
646
|
+
returns table(escalation_id uuid, status text, task_event_sequence bigint, created_at timestamptz, updated_at timestamptz)
|
|
647
|
+
language plpgsql
|
|
648
|
+
security definer
|
|
649
|
+
set search_path = agent_board, public
|
|
650
|
+
as $$
|
|
651
|
+
declare
|
|
652
|
+
v_escalation_id uuid;
|
|
653
|
+
v_event record;
|
|
654
|
+
v_actor_agent_id uuid;
|
|
655
|
+
begin
|
|
656
|
+
v_actor_agent_id := agent_board._resolved_actor_agent_id(p_raised_by_agent_id, 'p_raised_by_agent_id');
|
|
657
|
+
|
|
658
|
+
if not agent_board._current_agent_can_access_thread(p_thread_id) then
|
|
659
|
+
raise exception 'thread not accessible to current agent' using errcode = '42501';
|
|
660
|
+
end if;
|
|
661
|
+
|
|
662
|
+
insert into agent_board.escalations(thread_id, task_id, raised_by_agent_id, assigned_agent_id, summary, escalation_type, severity, details)
|
|
663
|
+
values (p_thread_id, p_task_id, v_actor_agent_id, p_assigned_agent_id, p_summary, coalesce(p_escalation_type, 'other'), coalesce(p_severity, 'medium'), coalesce(p_details, '{}'::jsonb))
|
|
664
|
+
returning id into v_escalation_id;
|
|
665
|
+
|
|
666
|
+
if p_task_id is not null then
|
|
667
|
+
select * into v_event
|
|
668
|
+
from agent_board.append_task_event(p_task_id, v_actor_agent_id, 'escalated', jsonb_build_object('escalation_id', v_escalation_id));
|
|
669
|
+
end if;
|
|
670
|
+
|
|
671
|
+
return query
|
|
672
|
+
select e.id, e.status, v_event.event_sequence, e.created_at, e.updated_at
|
|
673
|
+
from agent_board.escalations e
|
|
674
|
+
where e.id = v_escalation_id;
|
|
675
|
+
end;
|
|
676
|
+
$$;
|
|
677
|
+
|
|
678
|
+
create or replace function agent_board.resolve_escalation(
|
|
679
|
+
p_escalation_id uuid,
|
|
680
|
+
p_resolved_by_agent_id uuid,
|
|
681
|
+
p_details jsonb default '{}'::jsonb
|
|
682
|
+
)
|
|
683
|
+
returns table(escalation_id uuid, status text, resolved_at timestamptz, updated_at timestamptz, task_event_sequence bigint)
|
|
684
|
+
language plpgsql
|
|
685
|
+
security definer
|
|
686
|
+
set search_path = agent_board, public
|
|
687
|
+
as $$
|
|
688
|
+
declare
|
|
689
|
+
v_escalation agent_board.escalations%rowtype;
|
|
690
|
+
v_event record;
|
|
691
|
+
v_actor_agent_id uuid;
|
|
692
|
+
begin
|
|
693
|
+
v_actor_agent_id := agent_board._resolved_actor_agent_id(p_resolved_by_agent_id, 'p_resolved_by_agent_id');
|
|
694
|
+
|
|
695
|
+
select *
|
|
696
|
+
into v_escalation
|
|
697
|
+
from agent_board.escalations e
|
|
698
|
+
where e.id = p_escalation_id
|
|
699
|
+
and e.status <> 'resolved';
|
|
700
|
+
if v_escalation.id is null then
|
|
701
|
+
raise exception 'escalation not found' using errcode = 'P0002';
|
|
702
|
+
end if;
|
|
703
|
+
if not agent_board._current_agent_can_access_thread(v_escalation.thread_id) then
|
|
704
|
+
raise exception 'escalation not accessible to current agent' using errcode = '42501';
|
|
705
|
+
end if;
|
|
706
|
+
|
|
707
|
+
update agent_board.escalations
|
|
708
|
+
set status = 'resolved',
|
|
709
|
+
resolved_by_agent_id = v_actor_agent_id,
|
|
710
|
+
resolved_at = now(),
|
|
711
|
+
details = details || coalesce(p_details, '{}'::jsonb),
|
|
712
|
+
updated_at = now()
|
|
713
|
+
where id = p_escalation_id
|
|
714
|
+
returning * into v_escalation;
|
|
715
|
+
|
|
716
|
+
if v_escalation.task_id is not null then
|
|
717
|
+
select * into v_event
|
|
718
|
+
from agent_board.append_task_event(v_escalation.task_id, v_actor_agent_id, 'status_changed', jsonb_build_object('kind', 'escalation_resolved', 'escalation_id', p_escalation_id));
|
|
719
|
+
end if;
|
|
720
|
+
|
|
721
|
+
return query
|
|
722
|
+
select v_escalation.id, v_escalation.status, v_escalation.resolved_at, v_escalation.updated_at, v_event.event_sequence;
|
|
723
|
+
end;
|
|
724
|
+
$$;
|
|
725
|
+
|
|
726
|
+
create or replace function agent_board.mark_read(p_thread_id uuid, p_agent_id uuid, p_last_read_message_sequence bigint)
|
|
727
|
+
returns table(thread_id uuid, agent_id uuid, last_read_message_sequence bigint, last_read_at timestamptz)
|
|
728
|
+
language plpgsql
|
|
729
|
+
security definer
|
|
730
|
+
set search_path = agent_board, public
|
|
731
|
+
as $$
|
|
732
|
+
declare
|
|
733
|
+
v_sequence bigint;
|
|
734
|
+
v_actor_agent_id uuid;
|
|
735
|
+
begin
|
|
736
|
+
v_actor_agent_id := agent_board._resolved_actor_agent_id(p_agent_id, 'p_agent_id');
|
|
737
|
+
|
|
738
|
+
if p_last_read_message_sequence < 0 then
|
|
739
|
+
raise exception 'negative read sequence' using errcode = '22023';
|
|
740
|
+
end if;
|
|
741
|
+
|
|
742
|
+
if not agent_board._current_agent_can_access_thread(p_thread_id) then
|
|
743
|
+
raise exception 'thread not accessible to current agent' using errcode = '42501';
|
|
744
|
+
end if;
|
|
745
|
+
|
|
746
|
+
select least(p_last_read_message_sequence, t.last_message_sequence)
|
|
747
|
+
into v_sequence
|
|
748
|
+
from agent_board.threads t
|
|
749
|
+
where t.id = p_thread_id;
|
|
750
|
+
|
|
751
|
+
if v_sequence is null then
|
|
752
|
+
raise exception 'thread not found' using errcode = 'P0002';
|
|
753
|
+
end if;
|
|
754
|
+
|
|
755
|
+
return query
|
|
756
|
+
insert into agent_board.read_receipts(thread_id, agent_id, last_read_message_sequence, last_read_at)
|
|
757
|
+
values (p_thread_id, v_actor_agent_id, v_sequence, now())
|
|
758
|
+
on conflict on constraint read_receipts_pkey do update
|
|
759
|
+
set last_read_message_sequence = greatest(read_receipts.last_read_message_sequence, excluded.last_read_message_sequence),
|
|
760
|
+
last_read_at = now()
|
|
761
|
+
returning read_receipts.thread_id, read_receipts.agent_id, read_receipts.last_read_message_sequence, read_receipts.last_read_at;
|
|
762
|
+
end;
|
|
763
|
+
$$;
|
|
764
|
+
|
|
765
|
+
revoke all on function agent_board.register_agent(text, text, text, text, jsonb) from public, anon;
|
|
766
|
+
revoke all on function agent_board.heartbeat(uuid, uuid, text, text, interval, jsonb) from public, anon;
|
|
767
|
+
revoke all on function agent_board.create_thread(uuid, text, text, jsonb) from public, anon;
|
|
768
|
+
revoke all on function agent_board.post_message(uuid, uuid, text, text, uuid, jsonb) from public, anon;
|
|
769
|
+
revoke all on function agent_board.create_task(uuid, uuid, text, text, uuid, uuid, text, text, jsonb) from public, anon;
|
|
770
|
+
revoke all on function agent_board.append_task_event(uuid, uuid, text, jsonb, text, uuid, timestamptz) from public, anon;
|
|
771
|
+
revoke all on function agent_board.acquire_claim(uuid, text, text, timestamptz, uuid, uuid, jsonb) from public, anon;
|
|
772
|
+
revoke all on function agent_board.renew_claim(uuid, timestamptz) from public, anon;
|
|
773
|
+
revoke all on function agent_board.release_claim(uuid) from public, anon;
|
|
774
|
+
revoke all on function agent_board.delegate_task(uuid, uuid, text, text, uuid, text, text, jsonb) from public, anon;
|
|
775
|
+
revoke all on function agent_board.add_artifact(uuid, text, text, text, uuid, uuid, uuid, uuid, jsonb) from public, anon;
|
|
776
|
+
revoke all on function agent_board.raise_conflict(uuid, uuid, text, text, text, uuid, jsonb) from public, anon;
|
|
777
|
+
revoke all on function agent_board.resolve_conflict(uuid, uuid, jsonb) from public, anon;
|
|
778
|
+
revoke all on function agent_board.raise_escalation(uuid, uuid, text, text, text, uuid, uuid, jsonb) from public, anon;
|
|
779
|
+
revoke all on function agent_board.resolve_escalation(uuid, uuid, jsonb) from public, anon;
|
|
780
|
+
revoke all on function agent_board.mark_read(uuid, uuid, bigint) from public, anon;
|
|
781
|
+
|
|
782
|
+
grant execute on all functions in schema agent_board to service_role;
|
|
783
|
+
grant execute on function agent_board.register_agent(text, text, text, text, jsonb) to authenticated;
|
|
784
|
+
grant execute on function agent_board.heartbeat(uuid, uuid, text, text, interval, jsonb) to authenticated;
|
|
785
|
+
grant execute on function agent_board.create_thread(uuid, text, text, jsonb) to authenticated;
|
|
786
|
+
grant execute on function agent_board.post_message(uuid, uuid, text, text, uuid, jsonb) to authenticated;
|
|
787
|
+
grant execute on function agent_board.create_task(uuid, uuid, text, text, uuid, uuid, text, text, jsonb) to authenticated;
|
|
788
|
+
grant execute on function agent_board.append_task_event(uuid, uuid, text, jsonb, text, uuid, timestamptz) to authenticated;
|
|
789
|
+
grant execute on function agent_board.acquire_claim(uuid, text, text, timestamptz, uuid, uuid, jsonb) to authenticated;
|
|
790
|
+
grant execute on function agent_board.renew_claim(uuid, timestamptz) to authenticated;
|
|
791
|
+
grant execute on function agent_board.release_claim(uuid) to authenticated;
|
|
792
|
+
grant execute on function agent_board.delegate_task(uuid, uuid, text, text, uuid, text, text, jsonb) to authenticated;
|
|
793
|
+
grant execute on function agent_board.add_artifact(uuid, text, text, text, uuid, uuid, uuid, uuid, jsonb) to authenticated;
|
|
794
|
+
grant execute on function agent_board.raise_conflict(uuid, uuid, text, text, text, uuid, jsonb) to authenticated;
|
|
795
|
+
grant execute on function agent_board.resolve_conflict(uuid, uuid, jsonb) to authenticated;
|
|
796
|
+
grant execute on function agent_board.raise_escalation(uuid, uuid, text, text, text, uuid, uuid, jsonb) to authenticated;
|
|
797
|
+
grant execute on function agent_board.resolve_escalation(uuid, uuid, jsonb) to authenticated;
|
|
798
|
+
grant execute on function agent_board.mark_read(uuid, uuid, bigint) to authenticated;
|