agent-relay-server 0.62.3 → 0.63.1
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/docs/openapi.json +1 -1
- package/package.json +4 -2
- package/public/assets/{MessageBubble-DRrxfjyT.js → MessageBubble-Cl6ufPHw.js} +2 -2
- package/public/assets/MessageBubble-Cl6ufPHw.js.map +1 -0
- package/public/assets/{PlanGraphPanel-Dp2eYazz.js → PlanGraphPanel-BnfjmkQ8.js} +2 -2
- package/public/assets/{PlanGraphPanel-Dp2eYazz.js.map → PlanGraphPanel-BnfjmkQ8.js.map} +1 -1
- package/public/assets/{activity--t0U2yzq.js → activity-aFxvGLtd.js} +2 -2
- package/public/assets/{activity--t0U2yzq.js.map → activity-aFxvGLtd.js.map} +1 -1
- package/public/assets/{agent-profiles-BMtJYzxn.js → agent-profiles-B4mleUDz.js} +2 -2
- package/public/assets/{agent-profiles-BMtJYzxn.js.map → agent-profiles-B4mleUDz.js.map} +1 -1
- package/public/assets/{agents-Ck9AePlc.js → agents-DW5djToQ.js} +2 -2
- package/public/assets/{agents-Ck9AePlc.js.map → agents-DW5djToQ.js.map} +1 -1
- package/public/assets/{analytics-CzfK387L.js → analytics-CRi6thtg.js} +2 -2
- package/public/assets/{analytics-CzfK387L.js.map → analytics-CRi6thtg.js.map} +1 -1
- package/public/assets/{automation-Cgw8vtE8.js → automation-Do4OpauI.js} +2 -2
- package/public/assets/{automation-Cgw8vtE8.js.map → automation-Do4OpauI.js.map} +1 -1
- package/public/assets/{branch-state-badge-DnEEPtOP.js → branch-state-badge-D3W3w7zz.js} +2 -2
- package/public/assets/{branch-state-badge-DnEEPtOP.js.map → branch-state-badge-D3W3w7zz.js.map} +1 -1
- package/public/assets/{channels-Dc3L2MQs.js → channels-BytdR9NS.js} +2 -2
- package/public/assets/{channels-Dc3L2MQs.js.map → channels-BytdR9NS.js.map} +1 -1
- package/public/assets/{chat-Fc6iJ7sa.js → chat-CvaQ2czL.js} +2 -2
- package/public/assets/{chat-Fc6iJ7sa.js.map → chat-CvaQ2czL.js.map} +1 -1
- package/public/assets/{connectors-Dc2ttGWs.js → connectors-BLQDp8Bc.js} +2 -2
- package/public/assets/{connectors-Dc2ttGWs.js.map → connectors-BLQDp8Bc.js.map} +1 -1
- package/public/assets/{display-DzNvOu1j.js → display-B06I5sqa.js} +3 -3
- package/public/assets/display-B06I5sqa.js.map +1 -0
- package/public/assets/{formatted-body-impl-Bg-gx7rG.js → formatted-body-impl-0Lf8e1Ic.js} +2 -2
- package/public/assets/{formatted-body-impl-Bg-gx7rG.js.map → formatted-body-impl-0Lf8e1Ic.js.map} +1 -1
- package/public/assets/{index-BeDncjN4.js → index-C3su8ezq.js} +6 -6
- package/public/assets/{index-BeDncjN4.js.map → index-C3su8ezq.js.map} +1 -1
- package/public/assets/{insights-c08aW-7n.js → insights-64uExH1E.js} +2 -2
- package/public/assets/{insights-c08aW-7n.js.map → insights-64uExH1E.js.map} +1 -1
- package/public/assets/{integrations-BwpgcqnF.js → integrations-HyC1EaIp.js} +2 -2
- package/public/assets/{integrations-BwpgcqnF.js.map → integrations-HyC1EaIp.js.map} +1 -1
- package/public/assets/{maintenance-CTKP0kWB.js → maintenance-EVBpKH07.js} +2 -2
- package/public/assets/{maintenance-CTKP0kWB.js.map → maintenance-EVBpKH07.js.map} +1 -1
- package/public/assets/{managed-agents-BofCxo8d.js → managed-agents-BLZPxGuw.js} +2 -2
- package/public/assets/{managed-agents-BofCxo8d.js.map → managed-agents-BLZPxGuw.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-CQ-6BIb8.js → markdown-preview-impl-Bg6rCWYN.js} +2 -2
- package/public/assets/{markdown-preview-impl-CQ-6BIb8.js.map → markdown-preview-impl-Bg6rCWYN.js.map} +1 -1
- package/public/assets/{memory-DvfXjMoi.js → memory-Bjr8bgjj.js} +2 -2
- package/public/assets/{memory-DvfXjMoi.js.map → memory-Bjr8bgjj.js.map} +1 -1
- package/public/assets/{messages-C9VsPzZ_.js → messages-CRYw1F4z.js} +2 -2
- package/public/assets/{messages-C9VsPzZ_.js.map → messages-CRYw1F4z.js.map} +1 -1
- package/public/assets/{orchestrators-MffVueeE.js → orchestrators-D-co1vdp.js} +2 -2
- package/public/assets/{orchestrators-MffVueeE.js.map → orchestrators-D-co1vdp.js.map} +1 -1
- package/public/assets/{overview-BSVajefM.js → overview-Ddy5czri.js} +2 -2
- package/public/assets/{overview-BSVajefM.js.map → overview-Ddy5czri.js.map} +1 -1
- package/public/assets/{pairs-B5711-Cs.js → pairs-CFVM8uHL.js} +2 -2
- package/public/assets/{pairs-B5711-Cs.js.map → pairs-CFVM8uHL.js.map} +1 -1
- package/public/assets/{security-DkUwK9Vj.js → security-Df4_76bh.js} +2 -2
- package/public/assets/{security-DkUwK9Vj.js.map → security-Df4_76bh.js.map} +1 -1
- package/public/assets/{select-DrM2RRiT.js → select-Cq3BBnL9.js} +2 -2
- package/public/assets/{select-DrM2RRiT.js.map → select-Cq3BBnL9.js.map} +1 -1
- package/public/assets/{settings-DdKCb-vy.js → settings-DOLEYXZL.js} +2 -2
- package/public/assets/{settings-DdKCb-vy.js.map → settings-DOLEYXZL.js.map} +1 -1
- package/public/assets/{store-iTiheuTD.js → store-C1E8jmNE.js} +4 -4
- package/public/assets/store-C1E8jmNE.js.map +1 -0
- package/public/assets/{tasks-e1y1PFfO.js → tasks-BcsmPi9_.js} +2 -2
- package/public/assets/{tasks-e1y1PFfO.js.map → tasks-BcsmPi9_.js.map} +1 -1
- package/public/assets/{teams-BUcxoGC_.js → teams-BQrV3eKI.js} +2 -2
- package/public/assets/{teams-BUcxoGC_.js.map → teams-BQrV3eKI.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-CmZjQOB2.js → terminal-viewer-impl-Dle04qpH.js} +2 -2
- package/public/assets/{terminal-viewer-impl-CmZjQOB2.js.map → terminal-viewer-impl-Dle04qpH.js.map} +1 -1
- package/public/assets/{work-queue-CXBQx43n.js → work-queue-BLhTLg4o.js} +2 -2
- package/public/assets/{work-queue-CXBQx43n.js.map → work-queue-BLhTLg4o.js.map} +1 -1
- package/public/assets/{workspaces-D2EfZh9a.js → workspaces-4NCNTmRQ.js} +3 -3
- package/public/assets/{workspaces-D2EfZh9a.js.map → workspaces-4NCNTmRQ.js.map} +1 -1
- package/public/index.html +3 -3
- package/runner/src/adapter.ts +2 -1
- package/runner/src/config.ts +143 -5
- package/src/agent-ref.ts +8 -6
- package/src/artifact-storage.ts +3 -5
- package/src/automations/constants.ts +12 -0
- package/src/automations/cron.ts +129 -0
- package/src/automations/crud.ts +84 -0
- package/src/automations/dispatch.ts +275 -0
- package/src/automations/index.ts +5 -0
- package/src/automations/reconcile.ts +245 -0
- package/src/automations/storage.ts +181 -0
- package/src/automations/types.ts +16 -0
- package/src/automations/validation.ts +140 -0
- package/src/automations.ts +1 -1045
- package/src/bus.ts +3 -4
- package/src/cli/_shared.ts +5 -4
- package/src/cli/agent-detect.ts +6 -4
- package/src/cli/introspect.ts +3 -2
- package/src/cli/token.ts +2 -1
- package/src/cli/workspace.ts +2 -1
- package/src/config.ts +176 -1
- package/src/connectors.ts +3 -3
- package/src/db/activity.ts +1 -1
- package/src/db/agent-predicates.ts +9 -0
- package/src/db/agent-search.ts +2 -2
- package/src/db/agents.ts +13 -41
- package/src/db/artifacts.ts +8 -8
- package/src/db/channel-identity.ts +38 -0
- package/src/db/channels.ts +37 -61
- package/src/db/connection.ts +2 -1
- package/src/db/continuation-archives.ts +7 -7
- package/src/db/delivery.ts +7 -12
- package/src/db/inbox.ts +1 -1
- package/src/db/integrations.ts +4 -4
- package/src/db/issue-lifecycle.ts +6 -6
- package/src/db/mappers.ts +1 -2
- package/src/db/merge-lease.ts +3 -3
- package/src/db/message-claims.ts +16 -0
- package/src/db/message-queries.ts +8 -0
- package/src/db/message-reads.ts +26 -9
- package/src/db/messages.ts +17 -41
- package/src/db/notification-subscriptions.ts +2 -2
- package/src/db/orchestrators.ts +4 -4
- package/src/db/pair-maintenance.ts +9 -0
- package/src/db/pairs.ts +10 -18
- package/src/db/plans.ts +1 -1
- package/src/db/policy-runtime.ts +14 -0
- package/src/db/project-entries.ts +1 -1
- package/src/db/projects.ts +2 -2
- package/src/db/shared.ts +7 -0
- package/src/db/stats.ts +2 -2
- package/src/db/task-core.ts +18 -0
- package/src/db/task-maintenance.ts +24 -0
- package/src/db/tasks.ts +5 -17
- package/src/db/teams.ts +3 -3
- package/src/db/workspaces.ts +6 -6
- package/src/index.ts +6 -5
- package/src/issue-tracker/github.ts +3 -3
- package/src/maintenance/constants.ts +52 -0
- package/src/maintenance/events.ts +32 -0
- package/src/maintenance/index.ts +7 -0
- package/src/maintenance/jobs.ts +340 -0
- package/src/maintenance/orphaned-sessions.ts +86 -0
- package/src/maintenance/scheduler.ts +231 -0
- package/src/maintenance/teams.ts +65 -0
- package/src/maintenance/types.ts +31 -0
- package/src/maintenance/workspaces.ts +251 -0
- package/src/maintenance.ts +1 -1099
- package/src/mcp/identity.ts +131 -0
- package/src/mcp/index.ts +208 -0
- package/src/mcp/tools-messaging.ts +459 -0
- package/src/mcp/tools-spawn.ts +145 -0
- package/src/mcp/tools-workspace.ts +227 -0
- package/src/mcp/types.ts +24 -0
- package/src/mcp/validation.ts +104 -0
- package/src/mcp.ts +1 -1319
- package/src/notification-router.ts +2 -2
- package/src/notification-rules.ts +2 -2
- package/src/project-ingest.ts +3 -3
- package/src/project-instructions.ts +1 -1
- package/src/provider-config-store.ts +5 -4
- package/src/provider-state.ts +2 -4
- package/src/reviewer-pipeline.ts +1 -1
- package/src/routes/agent-sessions.ts +5 -4
- package/src/routes/orchestrator-proxy.ts +5 -4
- package/src/routes/workspaces.ts +5 -4
- package/src/security.ts +13 -7
- package/src/self-resume.ts +3 -7
- package/src/services/agent-action.ts +2 -2
- package/src/services/issue-activity.ts +0 -4
- package/src/services/issue-associations.ts +2 -2
- package/src/services/issue-lifecycle.ts +8 -11
- package/src/services/managed-running.ts +1 -1
- package/src/services/parity-harness.ts +3 -3
- package/src/services/plan-graph.ts +4 -7
- package/src/services/send-message.ts +16 -12
- package/src/services/shutdown-agent.ts +2 -2
- package/src/services/team-templates.ts +2 -2
- package/src/services/team.ts +2 -2
- package/src/settings/modules.ts +1 -1
- package/src/settings/registry.ts +3 -3
- package/src/spawn-command.ts +1 -1
- package/src/sse.ts +0 -4
- package/src/upgrade.ts +7 -9
- package/src/workspace-actions.ts +1 -1
- package/src/workspace-auto-merge.ts +4 -5
- package/src/workspace-claim.ts +1 -2
- package/src/workspace-orphans.ts +2 -7
- package/src/workspace-probe.ts +5 -8
- package/public/assets/MessageBubble-DRrxfjyT.js.map +0 -1
- package/public/assets/display-DzNvOu1j.js.map +0 -1
- package/public/assets/store-iTiheuTD.js.map +0 -1
package/src/db/channels.ts
CHANGED
|
@@ -15,10 +15,12 @@ import {
|
|
|
15
15
|
} from "../contracts";
|
|
16
16
|
import { STALE_TTL_MS, DAY_MS, CLAIM_LEASE_MS, POOL_CLAIM_LEASE_MS, WORKSPACE_MERGE_LEASE_MS } from "../config";
|
|
17
17
|
import { matchAgents } from "../agent-ref";
|
|
18
|
-
import {
|
|
18
|
+
import { isDeliveryAgent } from "./agent-predicates.ts";
|
|
19
|
+
import { channelAccountIdForAgent, channelProviderForAgent, expectedChannelAgentId } from "./channel-identity.ts";
|
|
19
20
|
import { ValidationError, getDb } from "./connection.ts";
|
|
20
|
-
import {
|
|
21
|
-
import { runningAgentForPolicy } from "./
|
|
21
|
+
import { rowToAgent } from "./mappers.ts";
|
|
22
|
+
import { runningAgentForPolicy } from "./policy-runtime.ts";
|
|
23
|
+
import { parseStringArray } from "./shared.ts";
|
|
22
24
|
import type {
|
|
23
25
|
AgentCard,
|
|
24
26
|
ActivityEvent,
|
|
@@ -82,7 +84,9 @@ import type {
|
|
|
82
84
|
WorkspaceStatus,
|
|
83
85
|
} from "../types";
|
|
84
86
|
|
|
85
|
-
export
|
|
87
|
+
export { channelProviderForAgent, expectedChannelAgentId } from "./channel-identity.ts";
|
|
88
|
+
|
|
89
|
+
function rowToChannelBinding(row: any): ChannelBinding {
|
|
86
90
|
const target = row.target_type === "broadcast"
|
|
87
91
|
? { type: "broadcast" } as ChannelRouteTarget
|
|
88
92
|
: { type: row.target_type, id: row.target_id } as ChannelRouteTarget;
|
|
@@ -105,7 +109,7 @@ export function rowToChannelBinding(row: any): ChannelBinding {
|
|
|
105
109
|
return binding;
|
|
106
110
|
}
|
|
107
111
|
|
|
108
|
-
|
|
112
|
+
function bindingTargetToLegacyTarget(target: ChannelRouteTarget): string {
|
|
109
113
|
if (target.type === "agent") return target.id;
|
|
110
114
|
if (target.type === "label") return `label:${target.id}`;
|
|
111
115
|
if (target.type === "team") return `team:${target.id}`;
|
|
@@ -118,7 +122,7 @@ export function bindingTargetToLegacyTarget(target: ChannelRouteTarget): string
|
|
|
118
122
|
return "";
|
|
119
123
|
}
|
|
120
124
|
|
|
121
|
-
|
|
125
|
+
function configuredChannelsForAgent(agent: AgentCard): string[] {
|
|
122
126
|
const channels = agent.meta?.channels;
|
|
123
127
|
if (!Array.isArray(channels)) return [];
|
|
124
128
|
return channels
|
|
@@ -127,7 +131,7 @@ export function configuredChannelsForAgent(agent: AgentCard): string[] {
|
|
|
127
131
|
.filter((item) => item.length > 0);
|
|
128
132
|
}
|
|
129
133
|
|
|
130
|
-
|
|
134
|
+
function channelEntryMatches(channelId: string, entry: string): boolean {
|
|
131
135
|
const normalized = entry.trim();
|
|
132
136
|
if (!normalized) return false;
|
|
133
137
|
if (normalized === channelId) return true;
|
|
@@ -135,27 +139,36 @@ export function channelEntryMatches(channelId: string, entry: string): boolean {
|
|
|
135
139
|
return channelId.startsWith(`${normalized}:`);
|
|
136
140
|
}
|
|
137
141
|
|
|
138
|
-
|
|
142
|
+
function agentCanServeChannel(agent: AgentCard, channelId?: string): boolean {
|
|
139
143
|
if (!channelId) return true;
|
|
140
144
|
const channels = configuredChannelsForAgent(agent);
|
|
141
145
|
if (channels.length === 0) return true;
|
|
142
146
|
return channels.some((entry) => channelEntryMatches(channelId, entry));
|
|
143
147
|
}
|
|
144
148
|
|
|
145
|
-
|
|
146
|
-
const
|
|
149
|
+
function getChannelAgent(id: string): AgentCard | null {
|
|
150
|
+
const row = getDb().query("SELECT * FROM agents WHERE id = ?").get(id) as any;
|
|
151
|
+
return row ? rowToAgent(row) : null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function listChannelAgents(): AgentCard[] {
|
|
155
|
+
return (getDb().query("SELECT * FROM agents ORDER BY last_seen DESC").all() as any[]).map(rowToAgent);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function channelTargetMatches(target: ChannelRouteTarget, channelId?: string): AgentCard[] {
|
|
159
|
+
const candidates = listChannelAgents().filter((agent) => (
|
|
147
160
|
agent.id !== "user" &&
|
|
148
161
|
agent.id !== "system" &&
|
|
149
162
|
agent.kind !== "channel" &&
|
|
150
163
|
agent.meta?.kind !== "channel"
|
|
151
164
|
));
|
|
152
165
|
if (target.type === "agent" || target.type === "orchestrator") {
|
|
153
|
-
const agent =
|
|
166
|
+
const agent = getChannelAgent(target.id);
|
|
154
167
|
return agent ? [agent] : [];
|
|
155
168
|
}
|
|
156
169
|
if (target.type === "policy") {
|
|
157
170
|
const agentId = runningAgentForPolicy(target.id);
|
|
158
|
-
const agent = agentId ?
|
|
171
|
+
const agent = agentId ? getChannelAgent(agentId) : null;
|
|
159
172
|
return agent ? [agent] : [];
|
|
160
173
|
}
|
|
161
174
|
const channelCandidates = candidates.filter((agent) => agentCanServeChannel(agent, channelId));
|
|
@@ -168,15 +181,15 @@ export function channelTargetMatches(target: ChannelRouteTarget, channelId?: str
|
|
|
168
181
|
return [];
|
|
169
182
|
}
|
|
170
183
|
|
|
171
|
-
|
|
184
|
+
function poolSelectorMatches(selector: string, candidates: AgentCard[]): AgentCard[] {
|
|
172
185
|
if (selector.startsWith("label:")) return candidates.filter((a) => a.label === selector.slice("label:".length));
|
|
173
186
|
if (selector.startsWith("tag:")) return candidates.filter((a) => a.tags.includes(selector.slice("tag:".length)));
|
|
174
187
|
if (selector.startsWith("cap:")) return candidates.filter((a) => a.capabilities.includes(selector.slice("cap:".length)));
|
|
175
|
-
const agent =
|
|
188
|
+
const agent = getChannelAgent(selector);
|
|
176
189
|
return agent ? [agent] : [];
|
|
177
190
|
}
|
|
178
191
|
|
|
179
|
-
|
|
192
|
+
function channelTargetMatchSnapshot(agent: AgentCard): ChannelTargetHealth["matches"][number] {
|
|
180
193
|
return {
|
|
181
194
|
id: agent.id,
|
|
182
195
|
name: agent.name,
|
|
@@ -189,15 +202,15 @@ export function channelTargetMatchSnapshot(agent: AgentCard): ChannelTargetHealt
|
|
|
189
202
|
};
|
|
190
203
|
}
|
|
191
204
|
|
|
192
|
-
|
|
205
|
+
function isHealthyChannelTarget(agent: AgentCard, now: number): boolean {
|
|
193
206
|
return isDeliveryAgent(agent) && agent.ready && agent.lastSeen > now - STALE_TTL_MS;
|
|
194
207
|
}
|
|
195
208
|
|
|
196
|
-
|
|
209
|
+
function describeTarget(target: ChannelRouteTarget): string {
|
|
197
210
|
return bindingTargetToLegacyTarget(target);
|
|
198
211
|
}
|
|
199
212
|
|
|
200
|
-
|
|
213
|
+
function channelTargetHealth(binding: ChannelBinding, now: number = Date.now()): ChannelTargetHealth {
|
|
201
214
|
const target = binding.target;
|
|
202
215
|
const targetLabel = describeTarget(target);
|
|
203
216
|
const matches = channelTargetMatches(target, binding.channelId);
|
|
@@ -209,7 +222,7 @@ export function channelTargetHealth(binding: ChannelBinding, now: number = Date.
|
|
|
209
222
|
if (matches.length === 0) return { status: "error", detail: `Pool ${targetLabel}: no eligible agents`, target, matches: [] };
|
|
210
223
|
return { status: "error", detail: `Pool ${targetLabel}: ${healthyEligibles.length} eligible but slot unclaimed`, target, matches: snapshots };
|
|
211
224
|
}
|
|
212
|
-
const holder =
|
|
225
|
+
const holder = getChannelAgent(binding.poolAgentId);
|
|
213
226
|
if (!holder || holder.status === "offline") {
|
|
214
227
|
return { status: "error", detail: `Pool ${targetLabel}: holder ${binding.poolAgentId} is offline`, target, matches: snapshots };
|
|
215
228
|
}
|
|
@@ -271,7 +284,7 @@ export function channelTargetHealth(binding: ChannelBinding, now: number = Date.
|
|
|
271
284
|
};
|
|
272
285
|
}
|
|
273
286
|
|
|
274
|
-
|
|
287
|
+
function rowToChannelSummary(row: any): ChannelSummary {
|
|
275
288
|
const binding = row.binding_id ? rowToChannelBinding({
|
|
276
289
|
id: row.binding_id,
|
|
277
290
|
channel_id: row.id,
|
|
@@ -309,44 +322,7 @@ export function rowToChannelSummary(row: any): ChannelSummary {
|
|
|
309
322
|
};
|
|
310
323
|
}
|
|
311
324
|
|
|
312
|
-
|
|
313
|
-
export function channelProviderForAgent(agent: AgentCard): string {
|
|
314
|
-
return stringValue(agent.meta?.provider) ??
|
|
315
|
-
stringValue(agent.meta?.channelType) ??
|
|
316
|
-
stringValue(agent.meta?.transport) ??
|
|
317
|
-
agent.tags.find((tag) => tag.startsWith("channel:"))?.slice("channel:".length) ??
|
|
318
|
-
"custom";
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
export function channelProviderForInput(input: Pick<RegisterAgentInput, "tags" | "meta">): string {
|
|
322
|
-
return stringValue(input.meta?.provider) ??
|
|
323
|
-
stringValue(input.meta?.channelType) ??
|
|
324
|
-
stringValue(input.meta?.transport) ??
|
|
325
|
-
(input.tags ?? []).find((tag) => tag.startsWith("channel:"))?.slice("channel:".length) ??
|
|
326
|
-
"custom";
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
export function channelAccountIdForAgent(agent: AgentCard, provider: string): string {
|
|
330
|
-
const accountId = stringValue(agent.meta?.accountId);
|
|
331
|
-
if (accountId) return accountId;
|
|
332
|
-
const prefix = `${provider}:`;
|
|
333
|
-
return agent.id.startsWith(prefix) ? agent.id.slice(prefix.length) : agent.id;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
export function channelAccountIdForInput(input: Pick<RegisterAgentInput, "id" | "meta">, provider: string): string {
|
|
337
|
-
const accountId = stringValue(input.meta?.accountId);
|
|
338
|
-
if (accountId) return accountId;
|
|
339
|
-
const prefix = `${provider}:`;
|
|
340
|
-
return input.id.startsWith(prefix) ? input.id.slice(prefix.length) : input.id;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
export function expectedChannelAgentId(input: Pick<RegisterAgentInput, "id" | "tags" | "meta">): string {
|
|
344
|
-
const provider = channelProviderForInput(input);
|
|
345
|
-
const accountId = channelAccountIdForInput(input, provider);
|
|
346
|
-
return `${provider}:${accountId}`;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
export function channelDirectionForAgent(agent: AgentCard): ChannelSummary["direction"] {
|
|
325
|
+
function channelDirectionForAgent(agent: AgentCard): ChannelSummary["direction"] {
|
|
350
326
|
const direction = stringValue(agent.meta?.direction);
|
|
351
327
|
return direction === "inbound" || direction === "outbound" || direction === "bidirectional" ? direction : "bidirectional";
|
|
352
328
|
}
|
|
@@ -497,7 +473,7 @@ export function upsertChannelBinding(input: {
|
|
|
497
473
|
return rowToChannelBinding(row);
|
|
498
474
|
}
|
|
499
475
|
|
|
500
|
-
|
|
476
|
+
interface PoolBindingChange {
|
|
501
477
|
bindingId: string;
|
|
502
478
|
channelId: string;
|
|
503
479
|
previousAgentId: string | null;
|
|
@@ -517,7 +493,7 @@ export function evaluatePoolBindings(now: number = Date.now()): PoolBindingChang
|
|
|
517
493
|
|
|
518
494
|
let holderValid = false;
|
|
519
495
|
if (currentAgentId) {
|
|
520
|
-
const holder =
|
|
496
|
+
const holder = getChannelAgent(currentAgentId);
|
|
521
497
|
if (holder && holder.status !== "offline" && holder.ready && holder.lastSeen > now - STALE_TTL_MS && agentCanServeChannel(holder, channelId)) {
|
|
522
498
|
if (currentEpoch === null || holder.epoch === currentEpoch) {
|
|
523
499
|
getDb().query("UPDATE channel_bindings SET pool_claim_expires_at = ? WHERE id = ?")
|
|
@@ -534,7 +510,7 @@ export function evaluatePoolBindings(now: number = Date.now()): PoolBindingChang
|
|
|
534
510
|
}
|
|
535
511
|
|
|
536
512
|
if (!holderValid) {
|
|
537
|
-
const candidates =
|
|
513
|
+
const candidates = listChannelAgents().filter((a) =>
|
|
538
514
|
a.id !== "user" && a.id !== "system" && a.kind !== "channel" && a.meta?.kind !== "channel"
|
|
539
515
|
);
|
|
540
516
|
const eligible = poolSelectorMatches(selector, candidates)
|
package/src/db/connection.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
|
+
import { DB_SLOW_QUERY_MS } from "../config";
|
|
2
3
|
|
|
3
4
|
// The shared SQLite handle. Set once by initDb() (schema.ts) and read everywhere
|
|
4
5
|
// else through getDb(). Kept module-private so the only mutation path is setDb().
|
|
@@ -33,7 +34,7 @@ export function applyConnectionPragmas(conn: Database): void {
|
|
|
33
34
|
// Slow-query log. Off unless AGENT_RELAY_DB_SLOW_MS is set (>0). Wraps the
|
|
34
35
|
// execution of the heavy dynamic queries so the next slow query surfaces before
|
|
35
36
|
// it wedges something (see issue #197 — the 8.6s reply-obligation query).
|
|
36
|
-
const SLOW_QUERY_MS =
|
|
37
|
+
const SLOW_QUERY_MS = DB_SLOW_QUERY_MS;
|
|
37
38
|
export function timedQuery<T>(label: string, run: () => T): T {
|
|
38
39
|
if (SLOW_QUERY_MS <= 0) return run();
|
|
39
40
|
const start = performance.now();
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { MAX_BODY_BYTES } from "../config";
|
|
1
|
+
import { MAX_BODY_BYTES, continuationArchiveFtsEnabled } from "../config";
|
|
2
2
|
import { getContinuationEnvelope } from "./continuations.ts";
|
|
3
3
|
import { getDb, ValidationError } from "./connection.ts";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
interface ContinuationArchive {
|
|
6
6
|
id: number;
|
|
7
7
|
ref: string;
|
|
8
8
|
agentId: string;
|
|
@@ -11,7 +11,7 @@ export interface ContinuationArchive {
|
|
|
11
11
|
createdAt: number;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
interface ContinuationArchiveSearchResult extends ContinuationArchive {
|
|
15
15
|
score: number | null;
|
|
16
16
|
searchMode: "fts5" | "like";
|
|
17
17
|
}
|
|
@@ -28,7 +28,7 @@ interface ContinuationArchiveRow {
|
|
|
28
28
|
const MAX_RECALL_LIMIT = 20;
|
|
29
29
|
|
|
30
30
|
export function ensureContinuationArchiveSearchSchema(): void {
|
|
31
|
-
if (
|
|
31
|
+
if (!continuationArchiveFtsEnabled()) return;
|
|
32
32
|
try {
|
|
33
33
|
getDb().query(`
|
|
34
34
|
CREATE VIRTUAL TABLE IF NOT EXISTS continuation_archive_segments
|
|
@@ -80,7 +80,7 @@ export function archiveContinuationSegment(input: {
|
|
|
80
80
|
return getContinuationArchive(Number(info.lastInsertRowid))!;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
function getContinuationArchive(id: number): ContinuationArchive | null {
|
|
84
84
|
const row = getDb().query("SELECT * FROM continuation_archives WHERE id = ?").get(id) as ContinuationArchiveRow | undefined;
|
|
85
85
|
return row ? rowToArchive(row) : null;
|
|
86
86
|
}
|
|
@@ -115,8 +115,8 @@ export function searchContinuationArchives(input: {
|
|
|
115
115
|
return searchContinuationArchivesLike(agentId, query, limit);
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
if (
|
|
118
|
+
function continuationArchiveSearchMode(): "fts5" | "like" {
|
|
119
|
+
if (!continuationArchiveFtsEnabled()) return "like";
|
|
120
120
|
const row = getDb().query(`
|
|
121
121
|
SELECT 1 FROM sqlite_master
|
|
122
122
|
WHERE type = 'table' AND name = 'continuation_archive_segments'
|
package/src/db/delivery.ts
CHANGED
|
@@ -16,10 +16,11 @@ import {
|
|
|
16
16
|
import { STALE_TTL_MS, DAY_MS, CLAIM_LEASE_MS, POOL_CLAIM_LEASE_MS, WORKSPACE_MERGE_LEASE_MS } from "../config";
|
|
17
17
|
import { matchAgents } from "../agent-ref";
|
|
18
18
|
import { getAgent, listAgents } from "./agents.ts";
|
|
19
|
-
import {
|
|
19
|
+
import { isDeliveryAgent } from "./agent-predicates.ts";
|
|
20
|
+
import { channelProviderForAgent } from "./channel-identity.ts";
|
|
20
21
|
import { getDb } from "./connection.ts";
|
|
21
22
|
import { MSG_SELECT, rowToMessage, rowToMessageDeliveryAttempt } from "./mappers.ts";
|
|
22
|
-
import { getMessage } from "./
|
|
23
|
+
import { getMessage } from "./message-queries.ts";
|
|
23
24
|
import type {
|
|
24
25
|
AgentCard,
|
|
25
26
|
ActivityEvent,
|
|
@@ -83,7 +84,7 @@ import type {
|
|
|
83
84
|
WorkspaceStatus,
|
|
84
85
|
} from "../types";
|
|
85
86
|
|
|
86
|
-
|
|
87
|
+
function queueDepthLimit(target: string): number {
|
|
87
88
|
const row = getDb().query("SELECT value FROM config WHERE namespace = 'system' AND key = 'message-queue'").get() as { value?: string } | undefined;
|
|
88
89
|
const parsed = row?.value ? parseJson<Record<string, unknown>>(row.value, {}) : {};
|
|
89
90
|
const perTarget = parsed?.maxDepthPerTarget;
|
|
@@ -124,13 +125,7 @@ export function enforceQueueLimit(target: string): void {
|
|
|
124
125
|
})();
|
|
125
126
|
}
|
|
126
127
|
|
|
127
|
-
export
|
|
128
|
-
return agent.status !== "offline" &&
|
|
129
|
-
agent.id !== "user" &&
|
|
130
|
-
agent.id !== "system" &&
|
|
131
|
-
agent.kind !== "channel" &&
|
|
132
|
-
agent.meta?.kind !== "channel";
|
|
133
|
-
}
|
|
128
|
+
export { isDeliveryAgent } from "./agent-predicates.ts";
|
|
134
129
|
|
|
135
130
|
export function isChannelAgentId(agentId: string): boolean {
|
|
136
131
|
const agent = getAgent(agentId);
|
|
@@ -242,7 +237,7 @@ export function getMessageDeliveryAttempts(messageId: number, limit = 50): Messa
|
|
|
242
237
|
`).all(messageId, safeLimit) as any[]).map(rowToMessageDeliveryAttempt);
|
|
243
238
|
}
|
|
244
239
|
|
|
245
|
-
|
|
240
|
+
function insertMessageDeliveryAttempt(
|
|
246
241
|
messageId: number,
|
|
247
242
|
input: {
|
|
248
243
|
agentId?: string;
|
|
@@ -269,7 +264,7 @@ export function insertMessageDeliveryAttempt(
|
|
|
269
264
|
);
|
|
270
265
|
}
|
|
271
266
|
|
|
272
|
-
|
|
267
|
+
function setMessageDeliveryState(
|
|
273
268
|
messageId: number,
|
|
274
269
|
input: {
|
|
275
270
|
status: MessageDeliveryStatus;
|
package/src/db/inbox.ts
CHANGED
|
@@ -212,7 +212,7 @@ export function createChatHistoryImport(input: {
|
|
|
212
212
|
return getChatHistoryImport(id)!;
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
|
|
215
|
+
function getChatHistoryImport(id: string): ChatHistoryImport | null {
|
|
216
216
|
const row = getDb().query("SELECT * FROM chat_history_imports WHERE id = ?").get(id) as any | undefined;
|
|
217
217
|
if (!row) return null;
|
|
218
218
|
const entries = (getDb().query(
|
package/src/db/integrations.ts
CHANGED
|
@@ -212,7 +212,7 @@ export function listIntegrationTaskStats(): IntegrationTaskStats[] {
|
|
|
212
212
|
}));
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
|
|
215
|
+
type IntegrationRegistryInput = {
|
|
216
216
|
name: string;
|
|
217
217
|
displayName?: string;
|
|
218
218
|
description?: string;
|
|
@@ -231,7 +231,7 @@ export type IntegrationRegistryInput = {
|
|
|
231
231
|
source?: "api" | "env" | "observed";
|
|
232
232
|
};
|
|
233
233
|
|
|
234
|
-
|
|
234
|
+
function rowToIntegrationRegistry(row: any): Omit<IntegrationSummary, "configured" | "observed" | "callbackConfigured" | "rateLimit" | "taskStats"> & {
|
|
235
235
|
source: "api" | "env" | "observed";
|
|
236
236
|
lastEventAt?: number;
|
|
237
237
|
lastTaskAt?: number;
|
|
@@ -312,7 +312,7 @@ export function upsertIntegrationRegistry(input: IntegrationRegistryInput): Retu
|
|
|
312
312
|
return getIntegrationRegistry(name)!;
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
-
|
|
315
|
+
function getIntegrationRegistry(name: string): ReturnType<typeof rowToIntegrationRegistry> | null {
|
|
316
316
|
const row = getDb().query("SELECT * FROM integration_registry WHERE name = ?").get(name) as any;
|
|
317
317
|
return row ? rowToIntegrationRegistry(row) : null;
|
|
318
318
|
}
|
|
@@ -321,7 +321,7 @@ export function listIntegrationRegistry(): ReturnType<typeof rowToIntegrationReg
|
|
|
321
321
|
return (getDb().query("SELECT * FROM integration_registry ORDER BY display_name COLLATE NOCASE, name COLLATE NOCASE").all() as any[]).map(rowToIntegrationRegistry);
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
-
|
|
324
|
+
function updateIntegrationObserved(name: string, eventAt: number): void {
|
|
325
325
|
const now = Date.now();
|
|
326
326
|
getDb().query(`
|
|
327
327
|
INSERT INTO integration_registry (name, enabled, scopes, targets, channels, tags, manifest, source, created_at, updated_at, last_event_at, last_task_at)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { getDb, ValidationError } from "./connection.ts";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
const ISSUE_LIFECYCLE_ACTIONS = ["close", "reopen"] as const;
|
|
4
|
+
const ISSUE_LIFECYCLE_STATUSES = ["succeeded", "failed"] as const;
|
|
5
|
+
type IssueLifecycleAction = (typeof ISSUE_LIFECYCLE_ACTIONS)[number];
|
|
6
|
+
type IssueLifecycleStatus = (typeof ISSUE_LIFECYCLE_STATUSES)[number];
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
interface IssueLifecycleActionRecord {
|
|
9
9
|
id: number;
|
|
10
10
|
issueRefId: string;
|
|
11
11
|
action: IssueLifecycleAction;
|
|
@@ -24,7 +24,7 @@ export interface IssueLifecycleActionRecord {
|
|
|
24
24
|
updatedAt: number;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
interface UpsertIssueLifecycleActionInput {
|
|
28
28
|
issueRefId: string;
|
|
29
29
|
action: IssueLifecycleAction;
|
|
30
30
|
landKey: string;
|
package/src/db/mappers.ts
CHANGED
|
@@ -14,8 +14,7 @@ import {
|
|
|
14
14
|
} from "../contracts";
|
|
15
15
|
import { STALE_TTL_MS, DAY_MS, CLAIM_LEASE_MS, POOL_CLAIM_LEASE_MS, WORKSPACE_MERGE_LEASE_MS } from "../config";
|
|
16
16
|
import { matchAgents } from "../agent-ref";
|
|
17
|
-
import { parseStringArray } from "./
|
|
18
|
-
import { artifactId } from "./artifacts.ts";
|
|
17
|
+
import { parseStringArray } from "./shared.ts";
|
|
19
18
|
import type {
|
|
20
19
|
AgentCard,
|
|
21
20
|
ActivityEvent,
|
package/src/db/merge-lease.ts
CHANGED
|
@@ -80,7 +80,7 @@ import type {
|
|
|
80
80
|
|
|
81
81
|
// --- Per-repo merge serialization lease (issue #157) -----------------------
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
interface MergeLeaseRecord {
|
|
84
84
|
repoRoot: string;
|
|
85
85
|
workspaceId: string;
|
|
86
86
|
commandId?: string;
|
|
@@ -89,7 +89,7 @@ export interface MergeLeaseRecord {
|
|
|
89
89
|
expiresAt: number;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
function rowToMergeLease(row: any): MergeLeaseRecord {
|
|
93
93
|
return {
|
|
94
94
|
repoRoot: row.repo_root,
|
|
95
95
|
workspaceId: row.workspace_id,
|
|
@@ -171,7 +171,7 @@ export interface MergePauseRecord {
|
|
|
171
171
|
expiresAt: number;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
|
|
174
|
+
function rowToMergePause(row: any): MergePauseRecord {
|
|
175
175
|
return {
|
|
176
176
|
id: row.id,
|
|
177
177
|
repoRoot: row.repo_root,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CLAIM_LEASE_MS } from "../config";
|
|
2
|
+
import { getDb } from "./connection.ts";
|
|
3
|
+
|
|
4
|
+
export function claimMessageRow(messageId: number, agentId: string, now: number): { ok: false; error: string } | { ok: true } {
|
|
5
|
+
const expiresAt = now + CLAIM_LEASE_MS;
|
|
6
|
+
// Idempotent: a same-agent re-claim refreshes the lease (preserving the original claimed_at).
|
|
7
|
+
// Without this, a delivery failure that locally drops the claim strands the message until the
|
|
8
|
+
// lease expires (#261) — the holder's retry would otherwise 409 against its own claim.
|
|
9
|
+
const result = getDb().query(
|
|
10
|
+
"UPDATE messages SET claimed_by = ?, claimed_at = COALESCE(claimed_at, ?), claim_expires_at = ? WHERE id = ? AND (claimed_by IS NULL OR claimed_by = ?)"
|
|
11
|
+
).run(agentId, now, expiresAt, messageId, agentId);
|
|
12
|
+
|
|
13
|
+
// Atomic: if changes === 0, someone else claimed it between our read and write
|
|
14
|
+
if (result.changes === 0) return { ok: false, error: "claim race — already claimed" };
|
|
15
|
+
return { ok: true };
|
|
16
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { getDb } from "./connection.ts";
|
|
2
|
+
import { MSG_SELECT, rowToMessage } from "./mappers.ts";
|
|
3
|
+
import type { Message } from "../types";
|
|
4
|
+
|
|
5
|
+
export function getMessage(id: number): Message | null {
|
|
6
|
+
const row = getDb().query(`${MSG_SELECT} WHERE m.id = ?`).get(id) as any;
|
|
7
|
+
return row ? rowToMessage(row) : null;
|
|
8
|
+
}
|
package/src/db/message-reads.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
import { STALE_TTL_MS, DAY_MS, CLAIM_LEASE_MS, POOL_CLAIM_LEASE_MS, WORKSPACE_MERGE_LEASE_MS } from "../config";
|
|
16
16
|
import { matchAgents } from "../agent-ref";
|
|
17
17
|
import { getAgent } from "./agents.ts";
|
|
18
|
-
import { sendMessageWithResult } from "./messages.ts";
|
|
18
|
+
import { getMessage, sendMessageWithResult } from "./messages.ts";
|
|
19
19
|
import { deleteArtifactLinksForEntity } from "./artifacts.ts";
|
|
20
20
|
import { getDb, timedQuery } from "./connection.ts";
|
|
21
21
|
import { legacyChannelTargets, isChannelAgentId } from "./delivery.ts";
|
|
@@ -369,7 +369,7 @@ export function pollMessages(query: PollQuery): Message[] {
|
|
|
369
369
|
return annotateAnsweredMessages(rows.map(rowToMessage), query.for);
|
|
370
370
|
}
|
|
371
371
|
|
|
372
|
-
|
|
372
|
+
function messageRequiresReply(message: Message): boolean {
|
|
373
373
|
// Server-owned notification flag (#283) wins over every kind/sender heuristic below: an
|
|
374
374
|
// explicit replyExpected:false is a fire-and-forget message that must never become an obligation.
|
|
375
375
|
if (message.replyExpected === false) return false;
|
|
@@ -387,13 +387,13 @@ export function recordField(value: unknown): Record<string, unknown> | undefined
|
|
|
387
387
|
return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : undefined;
|
|
388
388
|
}
|
|
389
389
|
|
|
390
|
-
|
|
390
|
+
function relayConversationId(message: Message): string | undefined {
|
|
391
391
|
const conversation = recordField(message.payload?.conversation);
|
|
392
392
|
const replyContext = recordField(message.payload?.replyContext);
|
|
393
393
|
return stringField(replyContext?.conversationId) ?? stringField(conversation?.id);
|
|
394
394
|
}
|
|
395
395
|
|
|
396
|
-
|
|
396
|
+
function isCoveredByLaterAgentResponse(message: Message, agentId: string): boolean {
|
|
397
397
|
// Order by id, not created_at: ids are monotonic insertion order, so this is
|
|
398
398
|
// robust when a reply lands in the same millisecond as the message it covers
|
|
399
399
|
// (created_at > … strictly would miss it, leaving the message wrongly pending).
|
|
@@ -414,7 +414,7 @@ export function isCoveredByLaterAgentResponse(message: Message, agentId: string)
|
|
|
414
414
|
});
|
|
415
415
|
}
|
|
416
416
|
|
|
417
|
-
|
|
417
|
+
function replyObligationFromMessage(message: Message, agentId: string): ReplyObligation {
|
|
418
418
|
return {
|
|
419
419
|
messageId: message.id,
|
|
420
420
|
agentId,
|
|
@@ -482,6 +482,25 @@ export function channelObligationAnsweredBy(
|
|
|
482
482
|
return open.length === 1 ? open[0]!.messageId : null;
|
|
483
483
|
}
|
|
484
484
|
|
|
485
|
+
/**
|
|
486
|
+
* Spawn report-back linking (#492): a spawned worker's actual task is delivered as the provider
|
|
487
|
+
* prompt, while `seedSpawnReplyObligation` creates a short Stop-hook contract message so the worker
|
|
488
|
+
* cannot go idle without reporting back. Workers commonly send their natural completion report
|
|
489
|
+
* directly to the spawning coordinator; when there is exactly one open spawn obligation from that
|
|
490
|
+
* coordinator, that send is the answer. Link it to the obligation at send time so the Stop hook does
|
|
491
|
+
* not nag the worker into re-sending the same verdict through `/reply`.
|
|
492
|
+
*/
|
|
493
|
+
export function spawnObligationAnsweredBy(
|
|
494
|
+
agentId: string,
|
|
495
|
+
spawnerAgentId: string,
|
|
496
|
+
): number | null {
|
|
497
|
+
const open = listPendingReplyObligations(agentId, 50).filter((obligation) => {
|
|
498
|
+
if (obligation.from !== spawnerAgentId) return false;
|
|
499
|
+
return getMessage(obligation.messageId)?.payload?.source === "spawn-obligation";
|
|
500
|
+
});
|
|
501
|
+
return open.length === 1 ? open[0]!.messageId : null;
|
|
502
|
+
}
|
|
503
|
+
|
|
485
504
|
/**
|
|
486
505
|
* #413 / vent #121 — seed a pending reply obligation for a freshly-registered spawned worker so
|
|
487
506
|
* it cannot strand its verdict. A spawned agent gets its task as the positional spawn prompt, NOT
|
|
@@ -490,7 +509,7 @@ export function channelObligationAnsweredBy(
|
|
|
490
509
|
* spawn command by its `spawnRequestId` (same lookup as `seedContinuationObjectiveFromSpawnPrompt`),
|
|
491
510
|
* and — only when the spawn carried `expectReply` — posts a message FROM the spawner TO the worker
|
|
492
511
|
* and marks it read, making it an obligation immediately. The Stop hook then refuses to let the
|
|
493
|
-
* worker idle until it
|
|
512
|
+
* worker idle until it reports back; a natural direct send to the spawner is linked at send time.
|
|
494
513
|
*
|
|
495
514
|
* Call only on first registration; idempotent regardless (idempotencyKey dedups a double-fire).
|
|
496
515
|
* Never throws — a failure here must not abort registration. Returns the obligation message id,
|
|
@@ -515,15 +534,13 @@ export function seedSpawnReplyObligation(
|
|
|
515
534
|
const params = row ? parseJson<unknown>(row.params, {}) : {};
|
|
516
535
|
if (!isRecord(params) || params.expectReply !== true) return null;
|
|
517
536
|
|
|
518
|
-
const objective = typeof params.prompt === "string" ? params.prompt.trim() : "";
|
|
519
537
|
const contract = `Report-back requested by ${spawnedBy}. When you finish your spawned task, reply here with your result so the coordinator is notified — you should not go idle before delivering it.`;
|
|
520
|
-
const body = objective ? `${contract}\n\nYour task was:\n${objective}` : contract;
|
|
521
538
|
|
|
522
539
|
const { message } = sendMessageWithResult({
|
|
523
540
|
from: spawnedBy,
|
|
524
541
|
to: agentId,
|
|
525
542
|
subject: "Report your result when done",
|
|
526
|
-
body,
|
|
543
|
+
body: contract,
|
|
527
544
|
replyExpected: true,
|
|
528
545
|
// `payload.source` is the server-owned "this requires a reply" signal (see messageRequiresReply).
|
|
529
546
|
payload: { source: "spawn-obligation", spawnRequestId },
|