agent-relay-server 0.67.1 → 0.68.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 +2 -2
- package/public/assets/{MessageBubble-CgL7SMC9.js → MessageBubble-DPcJxTJj.js} +2 -2
- package/public/assets/{MessageBubble-CgL7SMC9.js.map → MessageBubble-DPcJxTJj.js.map} +1 -1
- package/public/assets/{PlanGraphPanel-C9M1YUH3.js → PlanGraphPanel-BYmE8ldg.js} +2 -2
- package/public/assets/{PlanGraphPanel-C9M1YUH3.js.map → PlanGraphPanel-BYmE8ldg.js.map} +1 -1
- package/public/assets/{activity-C0ki3CXN.js → activity-BzU196gP.js} +2 -2
- package/public/assets/{activity-C0ki3CXN.js.map → activity-BzU196gP.js.map} +1 -1
- package/public/assets/agent-profiles-CQuoP8Nr.js +2 -0
- package/public/assets/agent-profiles-CQuoP8Nr.js.map +1 -0
- package/public/assets/{agents-DVTyhojU.js → agents-qH6q44nK.js} +2 -2
- package/public/assets/{agents-DVTyhojU.js.map → agents-qH6q44nK.js.map} +1 -1
- package/public/assets/{analytics-D6ffu1ji.js → analytics-EJH9GFID.js} +2 -2
- package/public/assets/{analytics-D6ffu1ji.js.map → analytics-EJH9GFID.js.map} +1 -1
- package/public/assets/{automation-CKLQvSB-.js → automation-Dahmxg07.js} +2 -2
- package/public/assets/{automation-CKLQvSB-.js.map → automation-Dahmxg07.js.map} +1 -1
- package/public/assets/{branch-state-badge-DrwCnoKR.js → branch-state-badge-C7H8HY22.js} +2 -2
- package/public/assets/{branch-state-badge-DrwCnoKR.js.map → branch-state-badge-C7H8HY22.js.map} +1 -1
- package/public/assets/{channels-BuqbbyaO.js → channels-VhlwES_A.js} +2 -2
- package/public/assets/{channels-BuqbbyaO.js.map → channels-VhlwES_A.js.map} +1 -1
- package/public/assets/{chat-XM7zLIrJ.js → chat-DJqvtoFT.js} +2 -2
- package/public/assets/{chat-XM7zLIrJ.js.map → chat-DJqvtoFT.js.map} +1 -1
- package/public/assets/{connectors-_LpoKQNb.js → connectors-C03pRoXx.js} +2 -2
- package/public/assets/{connectors-_LpoKQNb.js.map → connectors-C03pRoXx.js.map} +1 -1
- package/public/assets/{formatted-body-impl-BOtgnE2e.js → formatted-body-impl-DhvR8Z-A.js} +2 -2
- package/public/assets/{formatted-body-impl-BOtgnE2e.js.map → formatted-body-impl-DhvR8Z-A.js.map} +1 -1
- package/public/assets/index-Dh7NjR-l.css +2 -0
- package/public/assets/{index-CARLLWss.js → index-VGRP16ua.js} +6 -6
- package/public/assets/index-VGRP16ua.js.map +1 -0
- package/public/assets/{insights-BuhwfZK_.js → insights-v9noub5E.js} +2 -2
- package/public/assets/{insights-BuhwfZK_.js.map → insights-v9noub5E.js.map} +1 -1
- package/public/assets/{integrations-DCyxAv8c.js → integrations-BEa3ArUw.js} +2 -2
- package/public/assets/{integrations-DCyxAv8c.js.map → integrations-BEa3ArUw.js.map} +1 -1
- package/public/assets/{maintenance-FVXyLhl7.js → maintenance-DyRfVkhp.js} +2 -2
- package/public/assets/{maintenance-FVXyLhl7.js.map → maintenance-DyRfVkhp.js.map} +1 -1
- package/public/assets/{managed-agents-CaeHrwda.js → managed-agents-BSdMoBgG.js} +2 -2
- package/public/assets/{managed-agents-CaeHrwda.js.map → managed-agents-BSdMoBgG.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-Brm7jSxL.js → markdown-preview-impl-DidsrMmn.js} +2 -2
- package/public/assets/{markdown-preview-impl-Brm7jSxL.js.map → markdown-preview-impl-DidsrMmn.js.map} +1 -1
- package/public/assets/{memory-Drj5wn76.js → memory-DM8muGVW.js} +2 -2
- package/public/assets/{memory-Drj5wn76.js.map → memory-DM8muGVW.js.map} +1 -1
- package/public/assets/{messages-CSSnT5X4.js → messages-BqjAkcBF.js} +2 -2
- package/public/assets/{messages-CSSnT5X4.js.map → messages-BqjAkcBF.js.map} +1 -1
- package/public/assets/{orchestrators-FdF1O-b_.js → orchestrators-C_mjUDZg.js} +2 -2
- package/public/assets/{orchestrators-FdF1O-b_.js.map → orchestrators-C_mjUDZg.js.map} +1 -1
- package/public/assets/{overview-tqzRF2zO.js → overview-Cpu2plX-.js} +2 -2
- package/public/assets/{overview-tqzRF2zO.js.map → overview-Cpu2plX-.js.map} +1 -1
- package/public/assets/{pairs-CJiBSa2o.js → pairs--8_0A0lq.js} +2 -2
- package/public/assets/{pairs-CJiBSa2o.js.map → pairs--8_0A0lq.js.map} +1 -1
- package/public/assets/{projects-DtJmrhI0.js → projects-nCww9PG1.js} +2 -2
- package/public/assets/{projects-DtJmrhI0.js.map → projects-nCww9PG1.js.map} +1 -1
- package/public/assets/{security-CItvWUwq.js → security-CwgoEvJT.js} +2 -2
- package/public/assets/{security-CItvWUwq.js.map → security-CwgoEvJT.js.map} +1 -1
- package/public/assets/{select-CXen1oFN.js → select-Bhx6zSZr.js} +2 -2
- package/public/assets/{select-CXen1oFN.js.map → select-Bhx6zSZr.js.map} +1 -1
- package/public/assets/{settings-Dm2NtIMK.js → settings-BNQBnKXg.js} +2 -2
- package/public/assets/{settings-Dm2NtIMK.js.map → settings-BNQBnKXg.js.map} +1 -1
- package/public/assets/store-BB-YW24l.js +9 -0
- package/public/assets/store-BB-YW24l.js.map +1 -0
- package/public/assets/{tasks-DbyJbQLo.js → tasks-C5Kv18N7.js} +2 -2
- package/public/assets/{tasks-DbyJbQLo.js.map → tasks-C5Kv18N7.js.map} +1 -1
- package/public/assets/{teams-BtjasnAN.js → teams-D_Ttv7qD.js} +2 -2
- package/public/assets/{teams-BtjasnAN.js.map → teams-D_Ttv7qD.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-CbVuN1vi.js → terminal-viewer-impl-5y8v9jNx.js} +2 -2
- package/public/assets/{terminal-viewer-impl-CbVuN1vi.js.map → terminal-viewer-impl-5y8v9jNx.js.map} +1 -1
- package/public/assets/{work-queue-MP7UUAx7.js → work-queue-BB7bYLIK.js} +2 -2
- package/public/assets/{work-queue-MP7UUAx7.js.map → work-queue-BB7bYLIK.js.map} +1 -1
- package/public/assets/{workspaces-Bu4uVUcu.js → workspaces-CrtAeVnv.js} +2 -2
- package/public/assets/{workspaces-Bu4uVUcu.js.map → workspaces-CrtAeVnv.js.map} +1 -1
- package/public/index.html +3 -3
- package/runner/src/adapter.ts +11 -0
- package/src/agent-card-projection.ts +28 -0
- package/src/automations/dispatch.ts +2 -2
- package/src/bus.ts +5 -1
- package/src/config-store.ts +9 -3
- package/src/db/agent-search.ts +22 -8
- package/src/db/index.ts +1 -0
- package/src/db/migrations.ts +17 -0
- package/src/db/provisioning-registry.ts +278 -0
- package/src/db/schema.ts +16 -0
- package/src/managed-policy.ts +2 -1
- package/src/mcp/tools-messaging.ts +72 -3
- package/src/project-instructions.ts +41 -1
- package/src/routes/agent-sessions.ts +2 -2
- package/src/services/spawn-agent.ts +13 -17
- package/src/sse.ts +3 -4
- package/public/assets/agent-profiles-1Nx8ROA_.js +0 -2
- package/public/assets/agent-profiles-1Nx8ROA_.js.map +0 -1
- package/public/assets/index-CARLLWss.js.map +0 -1
- package/public/assets/index-eCpWfoCA.css +0 -2
- package/public/assets/store-BeKcuJ2q.js +0 -9
- package/public/assets/store-BeKcuJ2q.js.map +0 -1
package/src/db/agent-search.ts
CHANGED
|
@@ -103,6 +103,7 @@ export interface AgentSearchSort {
|
|
|
103
103
|
sortBy?: "lastActive" | "created" | "label";
|
|
104
104
|
order?: "asc" | "desc";
|
|
105
105
|
limit?: number;
|
|
106
|
+
offset?: number;
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
const AGENT_SEARCH_MAX_LIMIT = 200;
|
|
@@ -112,11 +113,8 @@ function searchValues(value: string | string[] | undefined): string[] {
|
|
|
112
113
|
return (Array.isArray(value) ? value : [value]).filter((v): v is string => typeof v === "string" && v.length > 0);
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
// (#219) and the running-only relay_agent_status default (#218). "running" mirrors the
|
|
118
|
-
// canonical liveness predicate (see getDb().ts delivery/steward checks + isAgentOnline).
|
|
119
|
-
export function searchAgents(filter: AgentSearchFilter = {}, sort: AgentSearchSort = {}): AgentCard[] {
|
|
116
|
+
// Shared clause builder — backs both searchAgents (paginated rows) and countAgents (total).
|
|
117
|
+
function buildWhereClauses(filter: AgentSearchFilter): { clauses: string[]; params: any[] } {
|
|
120
118
|
const clauses: string[] = ["1=1"];
|
|
121
119
|
const params: any[] = [];
|
|
122
120
|
const inList = (column: string, values: string[]) => {
|
|
@@ -166,15 +164,31 @@ export function searchAgents(filter: AgentSearchFilter = {}, sort: AgentSearchSo
|
|
|
166
164
|
if (explicit.length) inList("status", explicit);
|
|
167
165
|
}
|
|
168
166
|
|
|
167
|
+
return { clauses, params };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Unified fleet search: all filters optional and AND-combined; array values OR within a
|
|
171
|
+
// field. One parameterized query (no JS post-filter, no injection). Backs relay_find_agents
|
|
172
|
+
// (#219) and the running-only relay_agent_status default (#218). "running" mirrors the
|
|
173
|
+
// canonical liveness predicate (see getDb().ts delivery/steward checks + isAgentOnline).
|
|
174
|
+
export function searchAgents(filter: AgentSearchFilter = {}, sort: AgentSearchSort = {}): AgentCard[] {
|
|
175
|
+
const { clauses, params } = buildWhereClauses(filter);
|
|
169
176
|
const sortColumn = sort.sortBy === "created" ? "created_at" : sort.sortBy === "label" ? "label COLLATE NOCASE" : "last_seen";
|
|
170
177
|
const order = sort.order === "asc" ? "ASC" : "DESC";
|
|
171
178
|
const limit = Math.max(1, Math.min(sort.limit ?? 50, AGENT_SEARCH_MAX_LIMIT));
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const sql = `SELECT * FROM agents WHERE ${clauses.join(" AND ")} ORDER BY ${sortColumn} ${order} LIMIT ?`;
|
|
179
|
+
const offset = Math.max(0, sort.offset ?? 0);
|
|
180
|
+
params.push(limit, offset);
|
|
181
|
+
const sql = `SELECT * FROM agents WHERE ${clauses.join(" AND ")} ORDER BY ${sortColumn} ${order} LIMIT ? OFFSET ?`;
|
|
175
182
|
return (getDb().query(sql).all(...params) as any[]).map(rowToAgent);
|
|
176
183
|
}
|
|
177
184
|
|
|
185
|
+
// Total matching row count for the same filter — paired with searchAgents for pagination (#548).
|
|
186
|
+
export function countAgents(filter: AgentSearchFilter = {}): number {
|
|
187
|
+
const { clauses, params } = buildWhereClauses(filter);
|
|
188
|
+
const sql = `SELECT COUNT(*) as cnt FROM agents WHERE ${clauses.join(" AND ")}`;
|
|
189
|
+
return ((getDb().query(sql).get(...params) as { cnt: number } | null)?.cnt) ?? 0;
|
|
190
|
+
}
|
|
191
|
+
|
|
178
192
|
// Count of a parent's currently-LIVE children — the spawn quota numerator (#221). "Live"
|
|
179
193
|
// mirrors the running predicate (not offline/stale, ready, fresh); a child that exits frees
|
|
180
194
|
// a slot. Uncapped COUNT (unlike searchAgents' LIMIT) so the quota is exact at any fleet size.
|
package/src/db/index.ts
CHANGED
package/src/db/migrations.ts
CHANGED
|
@@ -540,6 +540,23 @@ export function applyMigrations(): void {
|
|
|
540
540
|
if (!agentColNames.includes("lifecycle")) {
|
|
541
541
|
getDb().run("ALTER TABLE agents ADD COLUMN lifecycle TEXT NOT NULL DEFAULT 'persistent'");
|
|
542
542
|
}
|
|
543
|
+
|
|
544
|
+
// Relay-owned provisioning registry (#554/#555/#556) — see schema.ts for the
|
|
545
|
+
// canonical DDL; mirrored here so existing DBs gain the table on upgrade.
|
|
546
|
+
getDb().run(`
|
|
547
|
+
CREATE TABLE IF NOT EXISTS provisioning_assets (
|
|
548
|
+
id TEXT PRIMARY KEY,
|
|
549
|
+
kind TEXT NOT NULL,
|
|
550
|
+
name TEXT NOT NULL,
|
|
551
|
+
provider TEXT NOT NULL DEFAULT 'any',
|
|
552
|
+
definition TEXT NOT NULL,
|
|
553
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
554
|
+
created_at INTEGER NOT NULL,
|
|
555
|
+
updated_at INTEGER NOT NULL,
|
|
556
|
+
UNIQUE(kind, name, provider)
|
|
557
|
+
)
|
|
558
|
+
`);
|
|
559
|
+
getDb().run("CREATE INDEX IF NOT EXISTS idx_provisioning_assets_kind ON provisioning_assets(kind, enabled)");
|
|
543
560
|
if (!agentColNames.includes("spawned_by")) {
|
|
544
561
|
getDb().run("ALTER TABLE agents ADD COLUMN spawned_by TEXT");
|
|
545
562
|
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { getDb, ValidationError } from "./connection.ts";
|
|
3
|
+
import { parseJson } from "../utils";
|
|
4
|
+
import { isRecord } from "agent-relay-sdk";
|
|
5
|
+
import type {
|
|
6
|
+
AgentProfile,
|
|
7
|
+
AgentProfileAssetRef,
|
|
8
|
+
AgentProfileProvider,
|
|
9
|
+
McpAssetDefinition,
|
|
10
|
+
PluginAssetDefinition,
|
|
11
|
+
ProvisioningAsset,
|
|
12
|
+
ProvisioningAssetKind,
|
|
13
|
+
ProvisioningMcpServer,
|
|
14
|
+
ResolvedAssetDefinition,
|
|
15
|
+
ResolvedProvisioning,
|
|
16
|
+
SkillAssetDefinition,
|
|
17
|
+
SpawnProvider,
|
|
18
|
+
} from "agent-relay-sdk";
|
|
19
|
+
|
|
20
|
+
// Relay-owned provisioning registry (#554/#555/#556). One table, one shape across
|
|
21
|
+
// skills/MCP/plugins: named definitions (the materializable content) keyed by
|
|
22
|
+
// (kind, name), selected by a profile's asset refs. The resolver below turns a
|
|
23
|
+
// profile's refs into a concrete ResolvedProvisioning bundle the runner writes into
|
|
24
|
+
// the launch. #557 (per-profile assembly) is meant to be a thin unifier on top, so
|
|
25
|
+
// this stays the single home for asset definition + resolution — not three modules.
|
|
26
|
+
|
|
27
|
+
const PROVISIONING_ASSET_KINDS = ["skill", "mcp", "plugin"] as const;
|
|
28
|
+
const PROVISIONING_PROVIDERS = ["claude", "codex", "any"] as const;
|
|
29
|
+
|
|
30
|
+
interface ProvisioningAssetRow {
|
|
31
|
+
id: string;
|
|
32
|
+
kind: ProvisioningAssetKind;
|
|
33
|
+
name: string;
|
|
34
|
+
provider: AgentProfileProvider;
|
|
35
|
+
definition: string;
|
|
36
|
+
enabled: number;
|
|
37
|
+
created_at: number;
|
|
38
|
+
updated_at: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface UpsertProvisioningAssetInput {
|
|
42
|
+
kind: ProvisioningAssetKind;
|
|
43
|
+
name: string;
|
|
44
|
+
provider?: AgentProfileProvider;
|
|
45
|
+
definition: ResolvedAssetDefinition;
|
|
46
|
+
enabled?: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function assetId(): string {
|
|
50
|
+
return `pa_${randomUUID().replace(/-/g, "").slice(0, 16)}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function assertKind(kind: ProvisioningAssetKind): void {
|
|
54
|
+
if (!PROVISIONING_ASSET_KINDS.includes(kind)) {
|
|
55
|
+
throw new ValidationError(`kind must be one of: ${PROVISIONING_ASSET_KINDS.join(", ")}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function assertProvider(provider: AgentProfileProvider): void {
|
|
60
|
+
if (!PROVISIONING_PROVIDERS.includes(provider)) {
|
|
61
|
+
throw new ValidationError(`provider must be one of: ${PROVISIONING_PROVIDERS.join(", ")}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function assertDefinition(kind: ProvisioningAssetKind, definition: ResolvedAssetDefinition): void {
|
|
66
|
+
if (!isRecord(definition)) throw new ValidationError("definition must be an object");
|
|
67
|
+
if (definition.kind !== kind) throw new ValidationError(`definition.kind (${definition.kind}) must match asset kind (${kind})`);
|
|
68
|
+
if (kind === "mcp" && !isRecord((definition as McpAssetDefinition).server)) {
|
|
69
|
+
throw new ValidationError("mcp definition requires a server object");
|
|
70
|
+
}
|
|
71
|
+
if ((kind === "skill" || kind === "plugin")) {
|
|
72
|
+
const def = definition as SkillAssetDefinition | PluginAssetDefinition;
|
|
73
|
+
if (!def.files?.length && !def.dir) throw new ValidationError(`${kind} definition requires files or dir`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function rowToAsset(row: ProvisioningAssetRow): ProvisioningAsset {
|
|
78
|
+
return {
|
|
79
|
+
id: row.id,
|
|
80
|
+
kind: row.kind,
|
|
81
|
+
name: row.name,
|
|
82
|
+
provider: row.provider,
|
|
83
|
+
definition: parseJson<ResolvedAssetDefinition>(row.definition, { kind: row.kind, name: row.name } as ResolvedAssetDefinition),
|
|
84
|
+
enabled: row.enabled === 1,
|
|
85
|
+
createdAt: row.created_at,
|
|
86
|
+
updatedAt: row.updated_at,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Insert or replace a named definition. Keyed by (kind, name, provider) so the same
|
|
91
|
+
// name can carry provider-specific variants (a claude skill vs a codex skill).
|
|
92
|
+
export function upsertProvisioningAsset(input: UpsertProvisioningAssetInput): ProvisioningAsset {
|
|
93
|
+
assertKind(input.kind);
|
|
94
|
+
const provider = input.provider ?? "any";
|
|
95
|
+
assertProvider(provider);
|
|
96
|
+
const name = input.definition.name || input.name;
|
|
97
|
+
if (!name) throw new ValidationError("name is required");
|
|
98
|
+
assertDefinition(input.kind, { ...input.definition, name });
|
|
99
|
+
const now = Date.now();
|
|
100
|
+
const existing = getDb()
|
|
101
|
+
.query("SELECT id, created_at FROM provisioning_assets WHERE kind = ? AND name = ? AND provider = ?")
|
|
102
|
+
.get(input.kind, name, provider) as { id: string; created_at: number } | undefined;
|
|
103
|
+
const id = existing?.id ?? assetId();
|
|
104
|
+
getDb().query(`
|
|
105
|
+
INSERT INTO provisioning_assets (id, kind, name, provider, definition, enabled, created_at, updated_at)
|
|
106
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
107
|
+
ON CONFLICT(kind, name, provider) DO UPDATE SET
|
|
108
|
+
definition = excluded.definition,
|
|
109
|
+
enabled = excluded.enabled,
|
|
110
|
+
updated_at = excluded.updated_at
|
|
111
|
+
`).run(
|
|
112
|
+
id,
|
|
113
|
+
input.kind,
|
|
114
|
+
name,
|
|
115
|
+
provider,
|
|
116
|
+
JSON.stringify({ ...input.definition, name }),
|
|
117
|
+
input.enabled === false ? 0 : 1,
|
|
118
|
+
existing?.created_at ?? now,
|
|
119
|
+
now,
|
|
120
|
+
);
|
|
121
|
+
return getProvisioningAsset(input.kind, name, provider)!;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function getProvisioningAsset(
|
|
125
|
+
kind: ProvisioningAssetKind,
|
|
126
|
+
name: string,
|
|
127
|
+
provider: AgentProfileProvider = "any",
|
|
128
|
+
): ProvisioningAsset | null {
|
|
129
|
+
const row = getDb()
|
|
130
|
+
.query("SELECT * FROM provisioning_assets WHERE kind = ? AND name = ? AND provider = ?")
|
|
131
|
+
.get(kind, name, provider) as ProvisioningAssetRow | undefined;
|
|
132
|
+
return row ? rowToAsset(row) : null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function listProvisioningAssets(opts: { kind?: ProvisioningAssetKind; includeDisabled?: boolean } = {}): ProvisioningAsset[] {
|
|
136
|
+
if (opts.kind) assertKind(opts.kind);
|
|
137
|
+
const conditions: string[] = [];
|
|
138
|
+
const params: (string | number)[] = [];
|
|
139
|
+
if (opts.kind) {
|
|
140
|
+
conditions.push("kind = ?");
|
|
141
|
+
params.push(opts.kind);
|
|
142
|
+
}
|
|
143
|
+
if (!opts.includeDisabled) conditions.push("enabled = 1");
|
|
144
|
+
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
145
|
+
const rows = getDb()
|
|
146
|
+
.query(`SELECT * FROM provisioning_assets ${where} ORDER BY kind, name, provider`)
|
|
147
|
+
.all(...params) as ProvisioningAssetRow[];
|
|
148
|
+
return rows.map(rowToAsset);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function deleteProvisioningAsset(
|
|
152
|
+
kind: ProvisioningAssetKind,
|
|
153
|
+
name: string,
|
|
154
|
+
provider: AgentProfileProvider = "any",
|
|
155
|
+
): boolean {
|
|
156
|
+
const result = getDb()
|
|
157
|
+
.query("DELETE FROM provisioning_assets WHERE kind = ? AND name = ? AND provider = ?")
|
|
158
|
+
.run(kind, name, provider);
|
|
159
|
+
return result.changes > 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Resolve a registry ref for a target provider: an exact provider match wins, else
|
|
163
|
+
// the provider-neutral "any" variant. Disabled definitions never resolve.
|
|
164
|
+
function resolveRegistryDefinition(
|
|
165
|
+
kind: ProvisioningAssetKind,
|
|
166
|
+
ref: string,
|
|
167
|
+
provider: SpawnProvider,
|
|
168
|
+
): ResolvedAssetDefinition | null {
|
|
169
|
+
const specific = getProvisioningAsset(kind, ref, provider);
|
|
170
|
+
if (specific?.enabled) return specific.definition;
|
|
171
|
+
const any = getProvisioningAsset(kind, ref, "any");
|
|
172
|
+
return any?.enabled ? any.definition : null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// A ref applies to a launch iff it's enabled and its provider scope includes the
|
|
176
|
+
// target ("any"/undefined → all providers).
|
|
177
|
+
function refAppliesToProvider(ref: AgentProfileAssetRef, provider: SpawnProvider): boolean {
|
|
178
|
+
return ref.enabled && (!ref.provider || ref.provider === "any" || ref.provider === provider);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function inlineFilesFromMeta(meta: Record<string, unknown> | undefined): { path: string; content: string }[] | undefined {
|
|
182
|
+
const raw = meta?.files;
|
|
183
|
+
if (!Array.isArray(raw)) return undefined;
|
|
184
|
+
const files = raw
|
|
185
|
+
.filter(isRecord)
|
|
186
|
+
.filter((f) => typeof f.path === "string" && typeof f.content === "string")
|
|
187
|
+
.map((f) => ({ path: f.path as string, content: f.content as string }));
|
|
188
|
+
return files.length ? files : undefined;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Resolve a skill/plugin asset ref into a concrete definition. relay → registry;
|
|
192
|
+
// inline → built from ref.meta (files or dir); repo → a cwd-relative dir token the
|
|
193
|
+
// runner resolves against the launch cwd; provider → skipped (provider built-in).
|
|
194
|
+
function resolveDirAssetRef(
|
|
195
|
+
kind: "skill" | "plugin",
|
|
196
|
+
ref: AgentProfileAssetRef,
|
|
197
|
+
provider: SpawnProvider,
|
|
198
|
+
): SkillAssetDefinition | PluginAssetDefinition | null {
|
|
199
|
+
if (ref.source === "relay") {
|
|
200
|
+
const def = resolveRegistryDefinition(kind, ref.ref, provider);
|
|
201
|
+
return def && def.kind === kind ? def : null;
|
|
202
|
+
}
|
|
203
|
+
if (ref.source === "inline") {
|
|
204
|
+
const files = inlineFilesFromMeta(ref.meta);
|
|
205
|
+
const dir = typeof ref.meta?.dir === "string" ? ref.meta.dir : undefined;
|
|
206
|
+
if (!files && !dir) return null;
|
|
207
|
+
return { kind, name: ref.ref, ...(files ? { files } : {}), ...(dir ? { dir } : {}) };
|
|
208
|
+
}
|
|
209
|
+
if (ref.source === "repo") {
|
|
210
|
+
return { kind, name: ref.ref, dir: ref.ref };
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// A profile.mcp.servers entry is either an inline server spec or a registry
|
|
216
|
+
// reference (`{ source: "relay", ref }`). Returns the concrete server spec.
|
|
217
|
+
function resolveMcpEntry(name: string, value: unknown, provider: SpawnProvider): ProvisioningMcpServer | null {
|
|
218
|
+
if (!isRecord(value)) return null;
|
|
219
|
+
if (value.source === "relay" && typeof value.ref === "string") {
|
|
220
|
+
const def = resolveRegistryDefinition("mcp", value.ref, provider);
|
|
221
|
+
return def && def.kind === "mcp" ? def.server : null;
|
|
222
|
+
}
|
|
223
|
+
if (value.enabled === false) return null;
|
|
224
|
+
// Inline spec: keep the recognized server fields only.
|
|
225
|
+
const server: ProvisioningMcpServer = {};
|
|
226
|
+
if (typeof value.type === "string") server.type = value.type as ProvisioningMcpServer["type"];
|
|
227
|
+
if (typeof value.command === "string") server.command = value.command;
|
|
228
|
+
if (Array.isArray(value.args)) server.args = value.args.filter((a): a is string => typeof a === "string");
|
|
229
|
+
if (isRecord(value.env)) server.env = stringRecord(value.env);
|
|
230
|
+
if (typeof value.url === "string") server.url = value.url;
|
|
231
|
+
if (isRecord(value.headers)) server.headers = stringRecord(value.headers);
|
|
232
|
+
return server.command || server.url ? server : null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function stringRecord(value: Record<string, unknown>): Record<string, string> {
|
|
236
|
+
const out: Record<string, string> = {};
|
|
237
|
+
for (const [k, v] of Object.entries(value)) {
|
|
238
|
+
if (typeof v === "string") out[k] = v;
|
|
239
|
+
}
|
|
240
|
+
return out;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Resolve a profile's declared skill/plugin/MCP refs against the registry into the
|
|
244
|
+
// concrete bundle the runner materializes. Relay-side (needs the DB); the result is
|
|
245
|
+
// attached to the in-flight profile (profile.provisioning) before it's serialized
|
|
246
|
+
// into the spawn env. Pure read — never mutates the registry.
|
|
247
|
+
export function resolveProfileProvisioning(profile: AgentProfile, provider: SpawnProvider): ResolvedProvisioning {
|
|
248
|
+
const skills: SkillAssetDefinition[] = [];
|
|
249
|
+
for (const ref of profile.skills ?? []) {
|
|
250
|
+
if (!refAppliesToProvider(ref, provider)) continue;
|
|
251
|
+
const def = resolveDirAssetRef("skill", ref, provider);
|
|
252
|
+
if (def) skills.push(def as SkillAssetDefinition);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const plugins: PluginAssetDefinition[] = [];
|
|
256
|
+
for (const ref of profile.plugins ?? []) {
|
|
257
|
+
if (!refAppliesToProvider(ref, provider)) continue;
|
|
258
|
+
const def = resolveDirAssetRef("plugin", ref, provider);
|
|
259
|
+
if (def) plugins.push(def as PluginAssetDefinition);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const mcpServers: Record<string, ProvisioningMcpServer> = {};
|
|
263
|
+
for (const [name, value] of Object.entries(profile.mcp?.servers ?? {})) {
|
|
264
|
+
const server = resolveMcpEntry(name, value, provider);
|
|
265
|
+
if (server) mcpServers[name] = server;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return { skills, plugins, mcpServers };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Attach the resolved bundle to a spawn-bound profile. Returns a shallow copy so the
|
|
272
|
+
// cached config-store value is never mutated. undefined passes through (an unresolved
|
|
273
|
+
// profile name stays undefined so callers' not-found guards still fire); the runner
|
|
274
|
+
// treats an absent and an empty bundle identically.
|
|
275
|
+
export function withResolvedProvisioning(profile: AgentProfile | undefined, provider: SpawnProvider): AgentProfile | undefined {
|
|
276
|
+
if (!profile) return undefined;
|
|
277
|
+
return { ...profile, provisioning: resolveProfileProvisioning(profile, provider) };
|
|
278
|
+
}
|
package/src/db/schema.ts
CHANGED
|
@@ -878,6 +878,22 @@ export function initDb(path: string = "agent-relay.db"): Database {
|
|
|
878
878
|
CREATE INDEX IF NOT EXISTS idx_insights_obs_project_signal ON insights_observations(project, signal, created_at DESC);
|
|
879
879
|
CREATE INDEX IF NOT EXISTS idx_insights_obs_signal ON insights_observations(signal, created_at DESC);
|
|
880
880
|
CREATE INDEX IF NOT EXISTS idx_insights_obs_session ON insights_observations(session_id);
|
|
881
|
+
|
|
882
|
+
-- Relay-owned provisioning registry (#554/#555/#556): named, materializable
|
|
883
|
+
-- skill/MCP/plugin definitions a vanilla profile selects by ref. (kind, name,
|
|
884
|
+
-- provider) is unique so a name can carry provider-specific variants.
|
|
885
|
+
CREATE TABLE IF NOT EXISTS provisioning_assets (
|
|
886
|
+
id TEXT PRIMARY KEY,
|
|
887
|
+
kind TEXT NOT NULL,
|
|
888
|
+
name TEXT NOT NULL,
|
|
889
|
+
provider TEXT NOT NULL DEFAULT 'any',
|
|
890
|
+
definition TEXT NOT NULL,
|
|
891
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
892
|
+
created_at INTEGER NOT NULL,
|
|
893
|
+
updated_at INTEGER NOT NULL,
|
|
894
|
+
UNIQUE(kind, name, provider)
|
|
895
|
+
);
|
|
896
|
+
CREATE INDEX IF NOT EXISTS idx_provisioning_assets_kind ON provisioning_assets(kind, enabled);
|
|
881
897
|
`);
|
|
882
898
|
|
|
883
899
|
ensureContinuationArchiveSearchSchema();
|
package/src/managed-policy.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getAgentProfile } from "./config-store";
|
|
2
|
+
import { withResolvedProvisioning } from "./db";
|
|
2
3
|
import { runnerRuntimeTokenEnv } from "./runtime-tokens";
|
|
3
4
|
import { buildSpawnCommand, resolveSpawnModelParams } from "./spawn-command";
|
|
4
5
|
import type { SpawnPolicy, WorkspaceMode } from "./types";
|
|
@@ -28,7 +29,7 @@ interface ManagedSpawnContext {
|
|
|
28
29
|
export function buildManagedSpawnParams(policy: SpawnPolicy, requestId: string, ctx: ManagedSpawnContext): Record<string, unknown> {
|
|
29
30
|
const providerArgs = managedPolicyProviderArgs(policy);
|
|
30
31
|
const prompt = managedPolicyLaunchPrompt(policy);
|
|
31
|
-
const agentProfile = policy.profile ? getAgentProfile(policy.profile)?.value : undefined;
|
|
32
|
+
const agentProfile = withResolvedProvisioning(policy.profile ? getAgentProfile(policy.profile)?.value : undefined, policy.provider);
|
|
32
33
|
return buildSpawnCommand({
|
|
33
34
|
provider: policy.provider,
|
|
34
35
|
orchestratorId: policy.orchestratorId,
|
|
@@ -4,6 +4,7 @@ import { bytesToStream } from "../http-body";
|
|
|
4
4
|
import { MAX_BODY_BYTES } from "../config";
|
|
5
5
|
import { getManagedAgentState, getSpawnPolicy, listSpawnPolicies } from "../config-store";
|
|
6
6
|
import {
|
|
7
|
+
countAgents,
|
|
7
8
|
createArtifact,
|
|
8
9
|
getAgent,
|
|
9
10
|
getArtifact,
|
|
@@ -20,6 +21,7 @@ import {
|
|
|
20
21
|
type AgentSearchFilter,
|
|
21
22
|
type AgentSearchSort,
|
|
22
23
|
} from "../db";
|
|
24
|
+
import type { AgentCard } from "../types";
|
|
23
25
|
import { type DeliveryReceipt } from "../agent-ref";
|
|
24
26
|
import { emitRelayEvent } from "../events";
|
|
25
27
|
import { McpAuthError, McpNotFoundError } from "../mcp-errors";
|
|
@@ -31,7 +33,7 @@ import { isRecord } from "agent-relay-sdk";
|
|
|
31
33
|
import type { ArtifactKind, ArtifactSensitivity, AttachmentRef, Message, SendMessageInput } from "../types";
|
|
32
34
|
import type { McpAuthContext, ToolDefinition } from "./types";
|
|
33
35
|
import { callerAgentId } from "./identity";
|
|
34
|
-
import { enumField, optionalAttachments, optionalBoolean, optionalFutureTimestamp, optionalPositiveInt, optionalRecord, optionalString, positiveId, recordField, stringField } from "./validation";
|
|
36
|
+
import { enumField, optionalAttachments, optionalBoolean, optionalFutureTimestamp, optionalNonNegativeInt, optionalPositiveInt, optionalRecord, optionalString, positiveId, recordField, stringField } from "./validation";
|
|
35
37
|
|
|
36
38
|
const VALID_ARTIFACT_KINDS = ["image", "audio", "video", "document", "archive", "other"] as const;
|
|
37
39
|
const VALID_ARTIFACT_SENSITIVITIES = ["public", "normal", "sensitive", "secret"] as const;
|
|
@@ -165,7 +167,7 @@ export const MESSAGING_TOOLS: ToolDefinition[] = [
|
|
|
165
167
|
},
|
|
166
168
|
{
|
|
167
169
|
name: "relay_find_agents",
|
|
168
|
-
description: "Search the fleet for agents by label, capability, provider, tag, machine, status, or kind, sorted by last-active/created/label. Defaults to running agents only. Use this to find a reachable, capable peer before messaging or pairing
|
|
170
|
+
description: "Search the fleet for agents by label, capability, provider, tag, machine, status, or kind, sorted by last-active/created/label. Returns a lean roster summary by default (id, label, status, machine, provider, model, lastSeen, spawnedBy, teamId) — add verbose:true for full agent cards. Defaults to running agents only. Use this to find a reachable, capable peer before messaging or pairing.",
|
|
169
171
|
requiredScopes: ["agents:read", "agent:read"],
|
|
170
172
|
inputSchema: {
|
|
171
173
|
type: "object",
|
|
@@ -182,6 +184,8 @@ export const MESSAGING_TOOLS: ToolDefinition[] = [
|
|
|
182
184
|
sortBy: { type: "string", enum: ["lastActive", "created", "label"] },
|
|
183
185
|
order: { type: "string", enum: ["asc", "desc"] },
|
|
184
186
|
limit: { type: "integer", minimum: 1, maximum: 200 },
|
|
187
|
+
offset: { type: "integer", minimum: 0, description: "Pagination offset (default 0). Use nextOffset from a prior response to page through results." },
|
|
188
|
+
verbose: { type: "boolean", description: "Return full agent cards instead of the lean roster summary. Even verbose results are paginated; use offset+limit to page." },
|
|
185
189
|
},
|
|
186
190
|
additionalProperties: false,
|
|
187
191
|
},
|
|
@@ -358,7 +362,42 @@ function optionalStringOrArray(value: unknown, field: string): string | string[]
|
|
|
358
362
|
throw new ValidationError(`${field} must be a string or string array`);
|
|
359
363
|
}
|
|
360
364
|
|
|
365
|
+
// Hard character cap for the lean list — a fat-card dump was 286K; lean entries are ~200 chars
|
|
366
|
+
// each so 200 entries ≈ 40K, well inside; this guard catches edge cases (#548).
|
|
367
|
+
const FIND_AGENTS_CHAR_CAP = 50_000;
|
|
368
|
+
|
|
369
|
+
// Fields a coordinator needs to reason about the roster — id, status, routing, parent, team.
|
|
370
|
+
// Drops providerCapabilities (multi-KB), context, meta, tags, capabilities, epoch, etc.
|
|
371
|
+
type AgentRosterEntry = {
|
|
372
|
+
id: string;
|
|
373
|
+
label?: string;
|
|
374
|
+
status: string;
|
|
375
|
+
machine?: string;
|
|
376
|
+
provider?: string;
|
|
377
|
+
model?: string;
|
|
378
|
+
lastSeen: number;
|
|
379
|
+
spawnedBy?: string;
|
|
380
|
+
spawnRequestId?: string;
|
|
381
|
+
teamId?: string;
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
function toLeanEntry(agent: AgentCard): AgentRosterEntry {
|
|
385
|
+
const entry: AgentRosterEntry = { id: agent.id, status: agent.status, lastSeen: agent.lastSeen };
|
|
386
|
+
if (agent.label) entry.label = agent.label;
|
|
387
|
+
if (agent.machine) entry.machine = agent.machine;
|
|
388
|
+
const pc = agent.providerCapabilities;
|
|
389
|
+
if (pc?.model?.provider) entry.provider = pc.model.provider;
|
|
390
|
+
if (pc?.model?.id) entry.model = pc.model.id;
|
|
391
|
+
if (agent.spawnedBy) entry.spawnedBy = agent.spawnedBy;
|
|
392
|
+
const srId = agent.meta?.spawnRequestId;
|
|
393
|
+
if (typeof srId === "string") entry.spawnRequestId = srId;
|
|
394
|
+
if (agent.teamId) entry.teamId = agent.teamId;
|
|
395
|
+
return entry;
|
|
396
|
+
}
|
|
397
|
+
|
|
361
398
|
export function relayFindAgents(auth: McpAuthContext, args: Record<string, unknown>): Record<string, unknown> {
|
|
399
|
+
const verbose = optionalBoolean(args.verbose, "verbose") === true;
|
|
400
|
+
const offset = optionalNonNegativeInt(args.offset, "offset") ?? 0;
|
|
362
401
|
const spawnedByArg = optionalString(args.spawnedBy, "spawnedBy", 240);
|
|
363
402
|
const spawnedBy = spawnedByArg === "me" ? callerAgentId(auth) ?? "\0unresolved" : spawnedByArg;
|
|
364
403
|
const filter: AgentSearchFilter = {
|
|
@@ -376,9 +415,39 @@ export function relayFindAgents(auth: McpAuthContext, args: Record<string, unkno
|
|
|
376
415
|
sortBy: optionalEnum(args.sortBy, "sortBy", ["lastActive", "created", "label"] as const),
|
|
377
416
|
order: optionalEnum(args.order, "order", ["asc", "desc"] as const),
|
|
378
417
|
limit: optionalPositiveInt(args.limit, "limit"),
|
|
418
|
+
offset: offset > 0 ? offset : undefined,
|
|
379
419
|
};
|
|
420
|
+
|
|
421
|
+
const total = countAgents(filter);
|
|
380
422
|
const agents = searchAgents(filter, sort);
|
|
381
|
-
|
|
423
|
+
const returned = agents.length;
|
|
424
|
+
const nextOffset = offset + returned < total ? offset + returned : undefined;
|
|
425
|
+
|
|
426
|
+
if (verbose) {
|
|
427
|
+
const result: Record<string, unknown> = { agents, total, count: returned };
|
|
428
|
+
if (nextOffset != null) result.nextOffset = nextOffset;
|
|
429
|
+
return result;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Default: lean projection — drop fat blobs, stay within token budget.
|
|
433
|
+
const lean = agents.map(toLeanEntry);
|
|
434
|
+
const encoded = JSON.stringify(lean);
|
|
435
|
+
if (encoded.length <= FIND_AGENTS_CHAR_CAP) {
|
|
436
|
+
const result: Record<string, unknown> = { agents: lean, total, count: returned };
|
|
437
|
+
if (nextOffset != null) result.nextOffset = nextOffset;
|
|
438
|
+
return result;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Hard cap: truncate lean list and emit explicit truncation hint.
|
|
442
|
+
const safe: AgentRosterEntry[] = [];
|
|
443
|
+
let size = 0;
|
|
444
|
+
for (const entry of lean) {
|
|
445
|
+
const s = JSON.stringify(entry);
|
|
446
|
+
if (size + s.length > FIND_AGENTS_CHAR_CAP) break;
|
|
447
|
+
safe.push(entry);
|
|
448
|
+
size += s.length;
|
|
449
|
+
}
|
|
450
|
+
return { agents: safe, total, count: safe.length, truncated: true, nextOffset: offset + safe.length };
|
|
382
451
|
}
|
|
383
452
|
|
|
384
453
|
function payloadWithAttachments(
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// is that lever: the included-source set is a policy PARAMETER, and the `vanilla`
|
|
12
12
|
// policy includes `manual`+`promoted` while excluding `ingested`.
|
|
13
13
|
import { resolveProjectEntries } from "./db";
|
|
14
|
-
import { PROJECT_ENTRY_SOURCES, type Project, type ProjectEntryKind, type ProjectEntrySource, type ResolvedProjectEntry } from "./types";
|
|
14
|
+
import { PROJECT_ENTRY_SOURCES, type AgentProfile, type Project, type ProjectEntryKind, type ProjectEntrySource, type ResolvedProjectEntry } from "./types";
|
|
15
15
|
|
|
16
16
|
/** Named source policies — THE additive-from-empty lever (#403 decision 4).
|
|
17
17
|
* `all` — every source (manual + promoted + ingested host-CLAUDE.md content).
|
|
@@ -150,6 +150,46 @@ export function composeProjectInstructions(
|
|
|
150
150
|
return out;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
/**
|
|
154
|
+
* #557 — THE instruction-sources half of the launch-assembly contract (the relay-side
|
|
155
|
+
* composer; the runner-side launch-assembly.ts assembles the provisioned skills/plugins/MCP
|
|
156
|
+
* + vanilla floor). It composes a spawned agent's systemPromptAppend from declared sources,
|
|
157
|
+
* in precedence order: project knowledge preamble (#409, the project-scoped channel) →
|
|
158
|
+
* profile system prompt → profile append entries → transient caller/fork context. Composing
|
|
159
|
+
* from EMPTY and adding (never reading host CLAUDE.md) is the additive-from-empty invariant.
|
|
160
|
+
*
|
|
161
|
+
* Returns the joined block, or the caller's own systemPromptAppend untouched when no
|
|
162
|
+
* profile/project material applies — so a profile-less spawn stays byte-identical.
|
|
163
|
+
*
|
|
164
|
+
* ── #559 SEAM ──────────────────────────────────────────────────────────────────────────
|
|
165
|
+
* Scoped {profile|user} memory does not exist yet (#559 generalizes Projects' anchor to
|
|
166
|
+
* project|profile|user). When it lands, a profile/user-scoped preamble composes in RIGHT
|
|
167
|
+
* HERE, beside the project preamble — same `composeProjectInstructions`-shaped renderer over
|
|
168
|
+
* the {profile|user} scope union, slotted into `appendBlocks` before the profile system
|
|
169
|
+
* prompt. The runner reports the contributing scopes via AssembledInstructions.scopedKnowledge.
|
|
170
|
+
* Until #559 lands, the only knowledge scope is the project preamble below — a thin add, not
|
|
171
|
+
* a refactor.
|
|
172
|
+
*/
|
|
173
|
+
export function composeSpawnInstructions(input: {
|
|
174
|
+
callerProject?: Pick<Project, "id" | "slug" | "title">;
|
|
175
|
+
projectInstructions?: ProjectInstructionPolicy;
|
|
176
|
+
profile?: Pick<AgentProfile, "instructions">;
|
|
177
|
+
callerSystemPromptAppend?: string;
|
|
178
|
+
}): string | undefined {
|
|
179
|
+
const appendBlocks: string[] = [];
|
|
180
|
+
if (input.projectInstructions && input.callerProject) {
|
|
181
|
+
const block = composeProjectInstructions(input.callerProject, {
|
|
182
|
+
...(input.projectInstructions.policy ? { policy: input.projectInstructions.policy } : {}),
|
|
183
|
+
});
|
|
184
|
+
if (block.trim()) appendBlocks.push(block);
|
|
185
|
+
}
|
|
186
|
+
// [#559 seam] profile/user-scoped knowledge preamble composes in here.
|
|
187
|
+
if (input.profile?.instructions.system?.trim()) appendBlocks.push(input.profile.instructions.system);
|
|
188
|
+
appendBlocks.push(...(input.profile?.instructions.append.filter((entry) => entry.trim()) ?? []));
|
|
189
|
+
if (input.callerSystemPromptAppend?.trim()) appendBlocks.push(input.callerSystemPromptAppend);
|
|
190
|
+
return appendBlocks.length ? appendBlocks.join("\n\n") : input.callerSystemPromptAppend;
|
|
191
|
+
}
|
|
192
|
+
|
|
153
193
|
/**
|
|
154
194
|
* Parse a spawn-transport's `projectInstructions` wire value into the normalized policy
|
|
155
195
|
* (or undefined = OFF). Shared by the MCP and HTTP spawn paths so they cannot drift.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Auto-split from routes.ts (#299). Domain: agent-sessions.
|
|
2
2
|
import { RELAY_TOKEN_HEADER, isRecord } from "agent-relay-sdk";
|
|
3
3
|
import { VALID_AGENT_ACTIONS, agentControlActionIcon, auditEvent, authAuditMetadata, authorizeRoute, cleanAttachmentRefs, dashboardAttribution, emitCommand, error, json, metaString, parseBody, spawnRequestId, type AgentControlAction, type Handler } from "./_shared";
|
|
4
|
-
import { ValidationError, getAgent, getAgentTimeline, getOrchestrator, markReady, mergeAgentMeta, sendMessage } from "../db";
|
|
4
|
+
import { ValidationError, getAgent, getAgentTimeline, getOrchestrator, markReady, mergeAgentMeta, sendMessage, withResolvedProvisioning } from "../db";
|
|
5
5
|
import { buildManagedSpawnParams } from "../managed-policy";
|
|
6
6
|
import { buildSpawnCommand, generateSpawnRequestId, resolveSpawnModelParams } from "../spawn-command";
|
|
7
7
|
import { cleanString, optionalEnum } from "../validation";
|
|
@@ -111,7 +111,7 @@ function restartSpawnParamsForAgent(
|
|
|
111
111
|
const effort = metaString(agent.meta, "effort") as ProviderEffort | undefined;
|
|
112
112
|
const providerArgs = metaStringArray(agent.meta, "providerArgs");
|
|
113
113
|
const profileName = metaString(agent.meta, "profile");
|
|
114
|
-
const agentProfile = profileName ? getAgentProfile(profileName)?.value : undefined;
|
|
114
|
+
const agentProfile = withResolvedProvisioning(profileName ? getAgentProfile(profileName)?.value : undefined, provider);
|
|
115
115
|
const workspaceMode = metaString(agent.meta, "workspaceMode") ?? "inherit";
|
|
116
116
|
const resolvedModel = resolveSpawnModelParams(provider, model, effort, { onError: "passthrough", skipDefaultWhenEmpty: true });
|
|
117
117
|
const params = buildSpawnCommand({
|